MySQL之select原理,UUID主键分析,插入或更新

1 MySQL查询

MySQL 作为互联网行业使用最多的关系型数据库之一,与其免费、开源的特性是密不可分的。然而,很多小伙伴工作了很多年,只知道使用 MySQL 进行 CRUD 操作,这也导致很多小伙伴工作多年后,想跳槽进入大厂,却在面试的时候屡屡碰壁。
问个简单的问题:select 语句是如何在 MySQL 中执行的? 这也是很多面试官喜欢问的问题,如果你连这个简单的问题都不能回答的话,那就要好好规划下自己的职业生涯了。
好了,今天我们就一起来聊聊 select 语句是如何在 MySQL 中执行的

1.1 sql语句的执行顺序

在这里插入图片描述

FROM
<left_table>

ON
<join_condition>

<join_type>
JOIN
<right_table>

WHERE
<where_condition>

GROUP BY
<group_by_list>

HAVING
<having_condition>

SELECT

DISTINCT
<select_list>

ORDER BY
<order_by_condition>

LIMIT
<limit_number>

1.2 频繁使用的select语句

为了更好地贯穿全文,这里先来列举一个最简单的 select 查询语句,例如:查询 user 表中 id1001 的用户信息,使用下面的 SQL 语句进行查询。

select * from user where user_id = 1001;

当我们在 MySQL 的命令行中输入上述 SQL 语句时,这条 SQL 语句到底在 MySQL 中是如何执行的呢?接下来,我们就以这条 SQL 语句为例,说说 select 语句是如何在 MySQL 中执行的。

1.3 MySQL 逻辑架构

在介绍 select 语句在 MySQL 中的执行流程之前,我们先来看看 MySQL 的逻辑架构,因为任何 SQL 语句的执行都离不开 MySQL 逻辑架构的支撑。也就是说, SQL 语句在 MySQL 中的执行流程与 MySQL 的逻辑架构是密不可分的
在这里插入图片描述

在上图中,我们简单地画了下 MySQL 的逻辑架构图,并且给出了逻辑分层和每层中各部分的功能。从逻辑上,我们可以将 MySQL 粗略地分成三层:Server 层存储引擎层系统文件层,而 Server 层中又可以分成网络连接层(连接器)数据服务层(Server 层)

Server 层中包含了连接器、查询缓存、分析器、优化器和执行器等 MySQL 的核心组成部分,另外,在 Server 层中还包含了所有的内置函数(比如:日期时间函数、加解密函数、聚合函数、数学函数等),存储引擎、触发器、视图等等。

存储引擎层主要负责和系统文件层进行交互,存储引擎层本身是插件式的架构设计,支持 InnoDB、MyISAM、Archive、Memory 等存储引擎。在 MySQL 5.5.5 及以后的版本中,MySQL 的默认存储引擎是 InnoDB。

系统文件层主要负责存储实际的数据,将数据以文件的形式存储到服务器的磁盘上。

接下来,我们就来说说一条 select 语句在 MySQL 的逻辑架构的每一部分到底是如何执行的

1.4 连接器是如何授权的

首先,我们先来看看在服务器命令行输入连接 MySQL 的命令时,MySQL 的连接器是如何进行验证的。比如,我们在服务器的命令行输入了如下命令

mysql -ubinghe -p

执行“回车”后,输入 binghe 账户的密码,与 MySQL 进行连接。此时,连接的过程需要完成经典的 TCP 握手操作。之后,连接器就开始认证连接的身份是否合法,最直接的就是验证用户名和密码是否正确。

如果用户名或者密码错误,MySQL 会提示 Access denied for user。如果用户名和密码正确,则连接器会到 MySQL 的权限表中查询当前连接拥有的权限。查询到权限之后,只要这个连接没有断开,则这个连接涉及到的权限操作都会依赖此时查询到的权限。

换句话说,一个用户登录 MySQL 并成功连接 MySQL 后,哪怕是管理员对当前用户的权限进行了修改操作,此时只要这个用户没有断开 MySQL 的连接,就不会受到管理修改权限的影响。管理员修改权限后,只有对新建的连接起作用。

如果客户端连接 MySQL 后,长时间没有执行任何操作,则连接器会自动断开与这个客户端的连接。具体多长时间断开是由 MySQL 的参数wait_timeout控制的,这个值默认是 8小时。我们可以根据实际业务需要,自行调整这个参数的值,以使 MySQL 能够满足我们的实际业务场景。

由于客户端与 MySQL 的连接是比较复杂的,这个过程也是比较耗时的,它会涉及 TCP 的握手操作,还会查询当前连接的权限信息等。往往在实际的工作过程中,我们会使用数据库连接池的方式,将数据库的连接缓存起来,这就意味着我们是使用长连接与 MySQL 进行交互的。

但是使用长连接连接 MySQL 也会有一个问题:那就是有时候会发现 MySQL 占用的内存涨得特别快,这是因为 MySQL 在执行的过程中,使用的临时内存是在连接对象里面进行管理的。这些占用的资源只有在连接断开的时候,才会被释放。如果连接长时间不释放,就会出现大量的临时内存占用内存空间。如果时间久了,可能会导致占用过多的内存,从而被操作系统“消灭”了,给人的感觉就是 MySQL 意外重启了。

我们可以使用如下的方案来解决这个问题:

  • 定期或者执行过一个比较占内存的查询操作后,断开连接,以后再重新建立和 MySQL 的连接
  • 如果使用 MySQL 5.7 或更新的 MySQL 版本,可以通过执行mysql_reset_connection重新初始化 MySQL 的资源。重新初始化的过程不会重新连接 MySQL,也不会重新做权限的验证操作。

1.5 查询缓存的作用是什么

登录 MySQL 后,客户端就会与 MySQL 建立连接,此时执行 select 语句时,首先会到查询缓存中查询是否执行过当前 select 语句。如果之前执行过相应的 select 语句,则执行过的 select 语句和查询结果会以 key-value 的形式存放在查询缓存中,其中,key 是查询语句,value 是查询的结果数据。

如果在查询缓存中没有找到相应的数据,则会继续执行后续的查询阶段。执行完成后,会将结果缓存到查询缓存中。后续的查询如果命中缓存,则直接返回查询缓存中的数据,性能还是挺高的。

但是,大多数时候我不太建议小伙伴们开启查询缓存,为啥?原因很简单:查询缓存失效的频率是非常频繁的,只要对一个表进行更新操作,则这张表上所有的查询缓存都会被清空。 而且在 MySQL 8.0 中,直接删除了查询缓存的功能

1.6 逻辑架构中各种分析器

1.6.1 分析器

分析器主要是对 select 语句进行 词法分析和语法分析 操作

如果 select 语句没有命中缓存,则首先会由分析器对其进行词法分析操作,此时,MySQL 会识别 select 语句中的每个字符串代表什么含义

例如,MySQL 会通过select关键字识别出这是一个查询语句,也会把user识别为数据表名 user,把id识别成字段名 id。接下来,就要进行“语法分析了”,根据语法规则,判断 select 语句是否满足 MySQL 的语法。如果判断出输入的 SQL 语句不满足语法规则,则 MySQL 会提示相应的错误信息。

1.6.2 优化器

select 语句进行了词法分析和语法分析后,还要经过优化器的优化处理才能执行。比如,我们的 select 语句中如果使用了多个索引,则优化器会决定使用哪个索引来查询数据;再比如,在 select 语句中,有多表关联的操作,优化器会决定各表的连接顺序,数据表的连接顺序不同,对于执行的效率会大不相同,优化器往往会选择使用查询效率高的连接顺序。

如果 select 语句经过优化器的优化之后,就会进入执行阶段了。

1.6.3 执行器

进入执行阶段的 select 语句,首先,执行器会对当前连接进行权限检查,最直接的方式就是检查当前连接是否对数据表 user 具有查询权限。如果当前连接对数据表 user 没有查询权限,就会返回没有权限的错误。例如,会返回如下错误。

ERROR 1142 (42000): SELECT command denied to user 'binghe'@'localhost' for table 'user'

如果当前连接具有对数据表 user 的查询权限,则会继续执行。首先会进行打开数据表的操作,此时优化器会根据创建表时使用的存储引擎,使用相应存储引擎的接口执行查询操作。这里,我们举一个例子:

假设,我们在 id 字段上没有建立索引,执行器执行的流程大致如下所示。

  1. 通过存储引擎读取数据表 user 的第一行数据,判断当前行的 id 值是否等于 1001,如果不等于 1001,则继续读取下一行数据;如果等于 1001,则将当前行放入结果集中。
  2. 继续通过存储引擎读取下一行数据,执行与(1)相同的逻辑判断,直到处理完 user 表中的所有数据。
  3. 处理完所有的数据后,执行器就会将结果集中的数据返回给客户端。

如果在 id 字段上有索引的话,执行的整体逻辑与 id 字段上没有索引大体一致。
如果开启了慢查询的话,执行 select 语句时,会在慢查询日志中输出一个 rows_examined 字段,这个字段表示 select 语句在执行的过程中扫描了数据表中的多少行数据。不过在有些场景下,执行器调用一次,存储引擎内部会会扫描多行,这就导致存储引擎扫描的行数与 rows_examined 字段标识的行数并不完全相同
转载于:https://mp.weixin.qq.com/s/bxVOPXU_ftZEVTxmW_6NAw

1.7 查询问题分析

1.7.1 limit耗时分析

点击了解MySQL中limit超大数据页分析

1.7.2 查询分组后前三条数据

点击了解MySQL获取分组后每组的前三条数据

1.7.3 SQL模式匹配

1.7.3.1 SQL模式

SQL的模式匹配允许你使用_匹配任何单个字符,而%匹配任意数目字符(包括零个字符)。在 MySQL中,SQL的模式缺省是忽略大小写的。下面显示一些例子。注意在你使用SQL模式时,你不能使用=或!=;而使用LIKENOT LIKE比较操作符。

SELECT 字段 FROM 表 WHERE 某字段 Like 条件

其中关于条件,SQL提供了四种匹配模式:

  1. %:表示任意个或多个字符。可匹配任意类型和长度的字符。
    比如 SELECT * FROM [user] WHERE u_name LIKE ‘%三%’
    将会把u_name为“张三”,“张猫三”、“三脚猫”,“唐三藏”等等有“三”的记录全找出来。另外,如果需要找出u_name中既有“三”又有“猫”的记录,请使用and条件SELECT * FROM [user] WHERE u_name LIKE ‘%三%’ AND u_name LIKE ‘%猫%’
  2. _:表示任意单个字符。匹配单个任意字符,它常用来限制表达式的字符长度语句:(可以代表一个中文字符)
    比如 SELECT * FROM [user] WHERE u_name LIKE ‘_三_’
    只找出“唐三藏”这样u_name为三个字且中间一个字是“三”的;
    再比如 SELECT * FROM [user] WHERE u_name LIKE ‘三__’;
    只找出“三脚猫”这样name为三个字且第一个字是“三”的;

1.7.3.2 正则模式

MySQL提供的模式匹配的其他类型是使用扩展正则表达式。当你对这类模式进行匹配测试时,使用REGEXPNOT REGEXP操作符(或RLIKENOT RLIKE,它们是同义词)。
扩展正则表达式的一些字符是:

  • . 匹配任何单个的字符。(单字节字符)
    一个字符类[…]匹配在方括号内的任何字符。例如,[abc]匹配“a”、“b”或“c”。为了命名字符的一个范围,使用一个-[a-z]匹配任何小写字母,而[0-9]匹配任何数字。
  • * 匹配零个多个在它前面的东西。例如,x*匹配任何数量的x字符,[0-9]*匹配的任何数量的数字,而.*匹配任何数量的任何东西。

正则表达式是区分大小写的,但是如果你希望,你能使用一个字符类匹配两种写法。例如,“[aA]”匹配小写或大写的“a”而“[a-zA-Z]”匹配两种写法的任何字母。如果它出现在被测试值的任何地方,模式就匹配(只要他们匹配整个值,SQL模式匹配)。
为了定位一个模式以便它必须匹配被测试值的开始或结尾,在模式开始处使用^或在模式的结尾用$
为了说明扩展正则表达式如何工作,上面所示的LIKE查询在下面使用REGEXP重写:
为了找出以开头的名字,使用^匹配名字的开始。
FROM [user] WHERE u_name REGEXP ‘^三’;将会把u_name为 “三脚猫”等等以“三”开头的记录全找出来。
为了找出以结尾的名字,使用$匹配名字的结尾。
FROM [user] WHERE u_name REGEXP ‘三$’;将会把u_name为“张三”,“张猫三”等等以“三”结尾的记录全找出来。
也可以使用{n}“重复n次”操作符重写先前的查询:FROM [user] WHERE u_name REGEXP ‘b{2}$’;

2 MySQL不能用UUID做主键

2.1 前言

mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复雪花id(long形且唯一,单机递增),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究竟有什么坏处?

2.2 mysql和程序实例

2.2.1 准备工作

要说明这个问题,我们首先来建立三张表
分别是user_auto_key,user_uuid,user_random_key分别表示自动增长的主键,uuid作为主键,随机key作为主键,其它我们完全保持不变.
根据控制变量法,我们只把每个表的主键使用不同的策略生成,而其他的字段完全一样,然后测试一下表的插入速度和查询速度:

注:这里的随机key其实是指用雪花算法算出来的前后不连续不重复无规律的id,一串18位长度的long值
id自动生成表:

create table user_key_auto(
	id int UNSIGNED not null auto_increment,
	user_id BIGINT(64) not NULL DEFAULT 0,
	user_name VARCHAR(64) not NULL DEFAULT '',
	sex int(2) not NULL,
	address VARCHAR(255) not null DEFAULT '',
	city VARCHAR(64) not NULL DEFAULT '',
	email VARCHAR(64) not nulT DEFAULT '',
	state int(6) not NULL DEFAULT 0,
	PRIMARY KEY(id),
	key user_name_key(user_name)
)ENGINE=INNODB

用户uuid表

create table user_uuid(
	id VARCHAR(36) not null,
	user_id BIGINT(64) not NULL DEFAULT 0,
	user_name VARCHAR(64) not NULL DEFAULT '',
	sex int(2) not NULL,
	address VARCHAR(255) not null DEFAULT '',
	city VARCHAR(64) not NULL DEFAULT '',
	email VARCHAR(64) not null DEFAULT '',
	state int(6) not NULL DEFAULT 0,
	PRIMARY KEY(id),
	key user_name_key(user_name)
)ENGINE=INNODB

随机主键表:

create table user_random_key(
	id BIGINT(64) not null DEFAULT 0,
	user_id BIGINT(64) not NULL DEFAULT 0,
	user_name VARCHAR(64) not NULL DEFAULT '',
	sex int(2) not NULL,
	address VARCHAR(255) not null DEFAULT '',
	city VARCHAR(64) not NULL DEFAULT '',
	email VARCHAR(64) not null DEFAULT '',
	state int(6) not NULL DEFAULT 0,
	PRIMARY KEY(id),
	key user_name_key(user_name)
)ENGINE=INNODB

2.2.2 开始测试

光有理论不行,直接上程序,使用spring的jdbcTemplate来实现增查测试:
技术框架:springboot+jdbcTemplate+junit+hutool,程序的原理就是连接自己的测试数据库,然后在相同的环境下写入同等数量的数据,来分析一下insert插入的时间来进行综合其效率,为了做到最真实的效果,所有的数据采用随机生成,比如名字、邮箱、地址都是随机生成。

import cn.hutool.core.collection.CollectionUtil;
import com.wyq.mysqldemo.databaseobject.UserKeyAuto;
import com.wyq.mysqldemo.databaseobject.UserKeyRandom;
import com.wyq.mysqldemo.databaseobject.UserKeyUUID;
import com.wyq.mysqldemo.diffkeytest.AutoKeyTableService;
import com.wyq.mysqldemo.diffkeytest.RandomKeyTableService;
import com.wyq.mysqldemo.diffkeytest.UUIDKeyTableService;
import com.wyq.mysqldemo.util.JdbcTemplateService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.StopWatch;
import java.util.List;
@SpringBootTest
class MysqlDemoApplicationTests {

    @Autowired
    private JdbcTemplateService jdbcTemplateService;

    @Autowired
    private AutoKeyTableService autoKeyTableService;

    @Autowired
    private UUIDKeyTableService uuidKeyTableService;

    @Autowired
    private RandomKeyTableService randomKeyTableService;


    @Test
    void testDBTime() {

        StopWatch stopwatch = new StopWatch("执行sql时间消耗");


        /**
         * auto_increment key任务
         */
        final String insertSql = "INSERT INTO user_key_auto(user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?)";

        List<UserKeyAuto> insertData = autoKeyTableService.getInsertData();
        stopwatch.start("自动生成key表任务开始");
        long start1 = System.currentTimeMillis();
        if (CollectionUtil.isNotEmpty(insertData)) {
            boolean insertResult = jdbcTemplateService.insert(insertSql, insertData, false);
            System.out.println(insertResult);
        }
        long end1 = System.currentTimeMillis();
        System.out.println("auto key消耗的时间:" + (end1 - start1));

        stopwatch.stop();


        /**
         * uudID的key
         */
        final String insertSql2 = "INSERT INTO user_uuid(id,user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?,?)";

        List<UserKeyUUID> insertData2 = uuidKeyTableService.getInsertData();
        stopwatch.start("UUID的key表任务开始");
        long begin = System.currentTimeMillis();
        if (CollectionUtil.isNotEmpty(insertData)) {
            boolean insertResult = jdbcTemplateService.insert(insertSql2, insertData2, true);
            System.out.println(insertResult);
        }
        long over = System.currentTimeMillis();
        System.out.println("UUID key消耗的时间:" + (over - begin));

        stopwatch.stop();


        /**
         * 随机的long值key
         */
        final String insertSql3 = "INSERT INTO user_random_key(id,user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?,?)";
        List<UserKeyRandom> insertData3 = randomKeyTableService.getInsertData();
        stopwatch.start("随机的long值key表任务开始");
        Long start = System.currentTimeMillis();
        if (CollectionUtil.isNotEmpty(insertData)) {
            boolean insertResult = jdbcTemplateService.insert(insertSql3, insertData3, true);
            System.out.println(insertResult);
        }
        Long end = System.currentTimeMillis();
        System.out.println("随机key任务消耗时间:" + (end - start));
        stopwatch.stop();


        String result = stopwatch.prettyPrint();
        System.out.println(result);
    }

2.2.3 程序写入结果

user_key_auto写入结果:
在这里插入图片描述

user_random_key写入结果:
在这里插入图片描述

user_uuid表写入结果:
在这里插入图片描述

2.2.4 效率测试结果

在这里插入图片描述

在已有数据量为130W的时候:我们再来测试一下插入10w数据,看看会有什么结果:
在这里插入图片描述

可以看出在数据量100W左右的时候,uuid的插入效率垫底,并且在后序增加了130W的数据,uuid的时间又直线下降

时间占用量总体可以打出的效率排名为:auto_key>random_key>uuid,uuid的效率最低,在数据量较大的情况下,效率直线下滑。那么为什么会出现这样的现象呢?带着疑问,我们来探讨一下这个问题:

2.3 使用uuid和自增id的索引结构对比

2.3.1 自增id

使用自增id的内部结构
在这里插入图片描述
自增的主键的值是顺序的,所以Innodb把每一条记录都存储在一条记录的后面。当达到页面的最大填充因子时候(innodb默认的最大填充因子是页大小的15/16,会留出1/16的空间留作以后的修改):

  • 下一条记录就会写入新的页中,一旦数据按照这种顺序的方式加载,主键页就会近乎于顺序的记录填满,提升了页面的最大填充率,不会有页的浪费
  • 新插入的行一定会在原有的最大数据行下一行,mysql定位和寻址很快,不会为计算新行的位置而做出额外的消耗
  • 减少了页分裂和碎片的产生

2.3.2 uuid

使用uuid的索引内部结构
在这里插入图片描述

因为uuid相对顺序的自增id来说是毫无规律可言的,新行的值不一定要比之前的主键的值要大,所以innodb无法做到总是把新行插入到索引的最后,而是需要为新行寻找新的合适的位置从而来分配新的空间。
这个过程需要做很多额外的操作,数据的毫无顺序会导致数据分布散乱,将会导致以下的问题:

  • 写入的目标页很可能已经刷新到磁盘上并且从缓存上移除,或者还没有被加载到缓存中,innodb在插入之前不得不先找到并从磁盘读取目标页到内存中,这将导致大量的随机IO
  • 因为写入是乱序的,innodb不得不频繁的做页分裂操作,以便为新的行分配空间,页分裂导致移动大量的数据,一次插入最少需要修改三个页以上
  • 由于频繁的页分裂,页会变得稀疏并被不规则的填充,最终会导致数据会有碎片
  • 在把随机值(uuid和雪花id)载入到聚簇索引(innodb默认的索引类型)以后,有时候会需要做一次OPTIMEIZE TABLE来重建表并优化页的填充,这将又需要一定的时间消耗。

结论:使用innodb应该尽可能的按主键的自增顺序插入,并且尽可能使用单调的增加的聚簇键的值来插入新行

2.4 自增id缺点

那么使用自增id就完全没有坏处了吗?并不是,自增id也会存在以下几点问题:

  • 别人一旦爬取你的数据库,就可以根据数据库的自增id获取到你的业务增长信息,很容易分析出你的经营情况
  • 对于高并发的负载,innodb在按主键进行插入的时候会造成明显的锁争用,主键的上界会成为争抢的热点,因为所有的插入都发生在这里,并发插入会导致间隙锁竞争
  • Auto_Increment锁机制会造成自增锁的抢夺,有一定的性能损失
    Auto_increment的锁争抢问题,如果要改善,需要调优innodb_autoinc_lock_mode的配置

点击了解MySQL自增锁机制

参考连接:https://mp.weixin.qq.com/s/px-vRWikt8xp7b3ZCr27uw

2.5 雪花算法

点击了解MySQL分布式环境下生成全局自增有序ID(雪花算法Snowflake)

3 插入或更新

MySQL中假如想要在插入时没有就插入有则更新,可以用MyISAM引擎的merge或者InnoDB引擎的on duplicate key,现在主要是用InnoDB,下面主要介绍on duplicate key

3.1 on duplicate key

3.1.1 定义

如果在INSERT语句末尾指定了 on duplicate key update,并且插入行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值,则在出现重复值的行执行UPDATE;如果不会导致唯一值列重复的问题,则插入新行。

3.1.2 values函数

values函数使用说明:在一个 INSERT … ON DUPLICATE KEY UPDATE … 语句中,可以在 UPDATE 子句中使用 VALUES(col_name ) 函数,用来访问来自该语句的 INSERT 部分的列值。换言之,UPDATE 子句中的 VALUES(col_name ) 访问需要被插入的 col_name 的值 , 并不会发生重复键冲突。这个函数在多行插入中特别有用。

values(col_name)函数只是取当前插入语句中的插入值,并没有累加功能。 如:count = values(count) 取前面 insert into 中的 count 值,并更新。
当有多条记录冲突,需要插入时,前面的更新值都被最后一条记录覆盖,所以呈现出取最后一条更新的现象。如:count = count + values(count) 依然取前面 insert into 中的 count 值,并与原记录值相加后更新回数据库,这样,当多条记录冲突需要插入时,就实现了不断累加更新的现象
注意VALUES() 函数只在 INSERT ... UPDATE 语句中有意义,而在其它情况下只会返回 NULL

3.1.3 注意事项

注意事项:

  • insert into ... on duplicate key update ... values() 这个语句,尽管在冲突时执行了更新,并没有插入,但是发现依然会占用 id 序号(自增),出现很多丢失的 id
  • 因为这是个插入语句,所以不能加where条件
  • 如果是插入操作,受到影响行的值为1;如果更新操作,受到影响的行的值为2;如果更新的数据和已有的数据比对一样(就相当于没变,所有值保持不变),受到影响的行的值为0
  • 更新的内容中unique key或者primary key最好按照一个来判断是否重复,不然不能保证语句执行正确(有任意一个unique key重复就会走更新);尽量不对存在多个唯一键的talbe使用该语句,避免可能导致数据错乱
  • 在有可能有并发事物执行的insert语句下不要使用该语句,可能导致产生dead lock
  • 如果数据表id是自动递增的不建议使用该语句;id不连续,如果前面更新的比较多,新增的下一条会相应跳跃的更大
  • 该语句是mysql独有的语法,如果可能涉及到其他数据库语言要慎重使用

3.1.4 操作分析

创建案例表 word_count(单词计数表)
  use test;
  CREATE TABLE IF NOT EXISTS word_count (
  	id int(11) NOT NULL AUTO_INCREMENT,
  	word varchar(64) NOT NULL,
  	count int(11) DEFAULT 0,
  	date date NOT NULL,
  	PRIMARY KEY (id),
  	UNIQUE KEY word (word, date)  // (word,date) 两字段组合唯一
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
  

执行第一次:(首次数据库表中没有数据,正常插入)
insert into word_count (word, count, date) values 
('a',5,curdate()) 
on duplicate key update count=values(count);
  # 结果显示:
  id   word    count    date 
  1    a       5        2023-03-11
  
执行第二次:(与第一次的唯一(word,date)冲突,执行更新)
insert into word_count (word, count, date) values 
('a',6,curdate()) 
on duplicate key update count=values(count);
  # 结果显示:
  id   word    count    date 
  1    a       6        2023-03-11  (更新)
  
执行第三次:
insert into word_count (word, count, date) values 
('a',6,curdate()-1),    // 取前一天,不会冲突
('a',7,curdate()) // 冲突
on duplicate key update count=values(count);
  # 结果显示:
  id   word    count    date 
  1    a       7        2023-03-11  (更新)
  3    a       6        2023-03-10  (新插入)
  
执行第四次:(更新冲突的最后一条插入值)
insert into word_count (word, count, date) values 
('a',2,curdate()),  // 冲突
('a',1,curdate())  // 冲突
on duplicate key update count=values(count);
  # 结果显示:
  id   word    count    date 
  1    a       1        2023-03-11  (更新最后一条插入值)
  3    a       6        2023-03-10  (不变)
  
执行第五次:(更新冲突的累加插入值)
insert into word_count (word, count, date) values 
('a',2,curdate()),
('a',1,curdate()) 
on duplicate key update count=count+values(count); // 实现每行累加
  # 结果显示:
  id   word    count    date 
  1    a       4        2023-03-11
  3    a       6        2023-03-10
 
执行第六次:(无冲突插入,观察 id 键值,出现了很多丢失,id 直接跳到了 9)
insert into word_count (word, count, date) values 
('b',2,curdate())
on duplicate key update count=count+values(count);
  # 结果显示:
  id   word    count    date 
  1    a       4        2019-06-26
  3    a       6        2019-06-25
  9    b       2        2019-06-26

3.2 replace info

3.2.1 定义

咱们在运用数据库时能够会常常遇到这种状况。若是一个表在一个字段上建立了索引,当咱们再向这个表中运用现已存在的键值插进一条记载,那将会抛出一个主键抵触的过错。当然,咱们能够想用新记载的值来掩盖本来的记载值。若是运用传统的做法,有必要先运用 DELETE 句子删去原先的记载,然后再运用 INSERT 插进新的记载。而在MySQL中为咱们供给了一种新的解决方案,这即是 REPLACE 句子。
运用REPLACE插进一条记载时,若是不重复,REPLACE就和INSERT的功用相同,若是有重复记载,REPLACE就运用新记载的值来更换本来的记载值。

运用REPLACE的最大优点即是能够将DELETEINSERT合二为一,构成一个原子操作。这样就能够不用思考在一起运用DELETEINSERT时增加业务等杂乱操作了。
在运用REPLACE时,表中有必要有索引,并且这个索引地点的字段不能答应空值,不然REPLACE就和INSERT彻底相同的

在履行REPLACE后,体系回来了所影响的行数,若是回来1,阐明在表中并没有重复的记载,若是回来2,阐明有一条重复记载,体系主动先调用了DELETE删去这条记载,然后再记载用INSERT来刺进这条记载。若是回来的值大于2,那阐明有多个仅有索引,有多条记载被删去和刺进。

replace具备替换拥有唯一索引或者主键索引重复数据的能力,也就是如果使用replace into插入的数据的唯一索引或者主键索引与之前的数据有重复的情况,将会删除原先的数据,然后再进行添加。
语法:replace into table( col1, col2, col3 ) values ( val1, val2, val3 )
语义:向table表中col1, col2, col3列replace数据val1,val2,val3

3.2.2 操作

先查询:select * from word_count
# 结果显示:
1	a	9	2023-03-11
3	a	7	2023-03-10
12	c	4	2023-03-11
13	b	1	2023-03-11

执行replace into 语句:
replace into word_count (word, count, date) values ('b',1,curdate());

再次查询:select * from word_count
# 结果显示:
1	a	9	2023-03-11
3	a	7	2023-03-10
12	c	4	2023-03-11
14	b	1	2023-03-11

3.2.3 MySQL中特殊插入语法

MySQL中的INSERT句子和规范的INSERT不太相同,在规范的SQL句子中,一次刺进一条记载的INSERT句子只要一种办法。

INSERT INTO tablename(列名…) VALUES(列值);

而在MySQL中还有别的一种办法,如下的方法把列名和列值成对呈现和运用,如下面的句子将产生中样的作用

INSERT INTO tablename SET column_name1 = value1, column_name2 =
value2,…;

3.3 INSERT IGNORE

MySQL中,可以使用INSERT IGNORE语句来忽略重复的记录。如果尝试插入的数据会导致重复键错误,那么INSERT IGNORE会跳过这条记录,而不是报错。

例如:

INSERT IGNORE INTO table (column1, column2) VALUES ('value1', 'value2');

在这个例子中,如果('value1', 'value2')会导致重复键错误,那么这条插入语句就会被忽略,而不会插入数据。同时,这条SQL语句也不会报错,而是会继续执行下一条SQL语句(如果有的话)。

需要注意的是,INSERT IGNORE不仅会忽略重复键错误,还会忽略其他一些错误(例如数据溢出)。因此,应当确保要插入的数据确实是想要插入的,并且确实希望在出错时跳过插入,而不是报错。

posted @ 2021-05-25 15:37  上善若泪  阅读(744)  评论(0编辑  收藏  举报