虽然我们习惯于给主键ID指定AUTO_INCREMENT属性,但是AUTO_INCREMENT也是可以指定到非主键字段的,唯一的约束就是这个字段上面得加索引,有了索引,就可以通过类似SELECT MAX(*ai_col*)的语句快速读到这列数据的最大值。

本文要探讨的话题是MySqlInnoDB引擎处理自增数据列的原理

MySql 5.1之前的实现

在这个版本之前,用AUTO_INCREMENT修饰的数据列确实是严格连续自增的。MySql的实现是会针对每个插入语句加一个全表维度的锁,这个锁可以保证每次只有一条插入语句在执行,每插入一行数据,就会生成一个自增数据。

mysql> CREATE TABLE t1 (-> c1 INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, -> c2 CHAR(1)-> ) ENGINE=InnoDB AUTO_INCREMENT=100;

假如我们在数据库中新建上面的这张表,接着我们执行插入语句。

mysql> INSERT INTO t1 (c1,c2) VALUES (NULL,'a'), (NULL,'b'), (NULL,'c'), (NULL,'d');

针对这条MySql执行的流程为:

  1. 全表加 AUTO-INC

    1.1 生成主键ID:101

    1.2 将行(101, 'a')插入表中

    1.3 生成主键ID: 102

    1.4 将行(102, 'b')插入表中

    ...

  2. 释放 AUTO-INC

MySql5.1之前的这种实现方式可以保证AUTO_INCREMENT严格自增,但是并发程度也最差,因为AUTO_INCREMENT锁是全表加锁直到这条语句结束

MySql 5.1版本带来的优化

前文中的insert语句是比较简单的,所谓简单的insert语句指的是插入的的数据行数是可以提前确定的,与之相对的是Bulk insert比如INSERT ... SELECT这类语句,这类插入语句的插入行数不能提前确定。

在这个版本以及之后,对于简单语句的插入,不再加全表的AUTO-INC锁,只会在产生自增列数据的时候加一个轻量级的互斥锁,等自增数据分配好,锁就释放了,因此像上面的例子,在MySql5.1之后的执行流程如下

  1. 加轻量级互斥锁

    1.1 分配自增数据

  2. 释放锁

  3. 将行(101, 'a')插入表中

  4. 将行(102, 'b')插入表中

    ...

可以看到,对于简单的插入语句,并发情况下的临界区变小了,且不再持有全表的锁,提升了并发性能。当然,如果在尝试加锁的过程中遇到有其他事务持有全表的AUTO-INC锁,还是要等待全表的AUTO-INC锁释放再执行本次插入操作

对于Bulk insert的插入语句,仍然避免不了全局的AUTO-INC锁,这类语句,他们的执行流程仍然保持和5.1之前版本一致,比如以下表为例

CREATE TABLE t1 (c1 INT(11) NOT NULL AUTO_INCREMENT,c2 VARCHAR(10) DEFAULT NULL,PRIMARY KEY (c1)
) ENGINE=InnoDB;

执行下面两条语句

Tx1: INSERT INTO t1 (c2) SELECT 1000 rows from another table ...
Tx2: INSERT INTO t1 (c2) VALUES ('xxx');

由于在执行Tx1时,InnoDB无法知道要插入的具体行数,因此会获取一个全表的锁,每执行一条插入语句就会给自增列赋新的值。因为有全表的锁,所以Tx1这条语句插入的所有行数都是连续自增的,Tx2自增列的值要么小于Tx1自增列的最小值,要么大于Tx1自增列中的最大值,这取决于这两条语句的执行顺序

InnoDB采取这样的决策一个重要的原因是主从复制,在MySql8.0之前,MySql的主从是基于语句复制的。在刚才的例子中,如果Tx1执行的时候没有全表的锁,那有可能在Tx1执行的过程中Tx2也在执行,这就会导致Tx1和Tx2自增列的数据每次执行结果都不相同,也就无法在从库中通过语句回放复制。

MySql 8.0版本之后的优化

虽然MySql5.1版本对简单的插入语句做了优化,避免了全表加锁,但对于INSERT ... SELECT这样的复杂插入语句,仍然避免不了全表的AUTO-INC锁,主要是基于执行语句的主从复制要能在从库完全回放复制主库,所有的语句执行结果就不能和执行顺序有关。

MySql 8.0以及之后默认的主从复制策略变成了基于数据行实现,在这样的背景下INSERT ... SELECT这样的复杂插入语句也不需要全表加锁来生成自增列数据了,所有的插入语句只有在生成自增列数据的时候要求持有一个轻量级的互斥锁,等到自增数据生成好之后释放锁。在这种实现下,所有插入语句的自增列都不能保证连续自增,但是并发性能确实最好的。

总结

需要说明的是,如果插入语句所处的事务回滚了,生成的自增列数据是不会回滚的,这种情况下会造成自增列数据非连续增长。

以上所述都是各个MySql版本的默认实现,MySql 5.1引入了一个新的参数 innodb_autoinc_lock_mode 通过修改这个字段的值,可以改变InnoDB生成自增列的策略,其值总结如下:

名称 含义
0 traditional lock mode 每次插入语句执行都会全表加锁至语句结束,5.1版本之前默认实现
1 consecutive lock mode 简单插入不再全表加锁,INSERT ... SELECT类的语句才持有全表锁,5.1至8.0默认实现
2 interleaved lock mode INSERT ... SELECT类的语句也不会全表加锁,只有生成自增列数据时才加锁,8.0之后默认实现

不推荐显式指定自增列数据,因为在5.7以及之前的版本,如果通过update语句显式指定一个比SELECT MAX(*ai_col*)还大的自增列值,后续insert语句可能会抛"Duplicate entry"错误,这一点在8.0版本之后也有了改变,如果通过显式的update语句显式指定一个比SELECT MAX(*ai_col*)还大的自增列值,那该值就会被持久化,后续的自增列值都从该值开始生成。

假如有下面这张表

mysql> CREATE TABLE t1 (-> c1 INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, -> c2 CHAR(1)-> ) ENGINE = INNODB AUTO_INCREMENT=100;

试想,在我们执行完下面这条语句之后表的内容变成了什么?

mysql> INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');

MySql 5.1之前,或者innodb_autoinc_lock_mode设置为0

mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
+-----+------+
| c1  | c2   |
+-----+------+
|   1 | a    |
| 101 | b    |
|   5 | c    |
| 102 | d    |
+-----+------+

在这种模式下,每插入一行数据就会生成一个自增值赋到c1这一行,因此c1的下一个自增值是103

MySql 8.0之前,或者innodb_autoinc_lock_mode设置为1

mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
+-----+------+
| c1  | c2   |
+-----+------+
|   1 | a    |
| 101 | b    |
|   5 | c    |
| 102 | d    |
+-----+------+

当前表的数据与前一个场景一致,但是下一个自增值却是105,因为在这个场景下,自增数据是在插入语句执行的最开始一次性生成的

MySql 8.0之后,或者innodb_autoinc_lock_mode设置为2

mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
+-----+------+
| c1  | c2   |
+-----+------+
|   1 | a    |
|   x | b    |
|   5 | c    |
|   y | d    |
+-----+------+

在这种场景下,因为同时可能有其他的插入语句执行,因此xy的值是不确定的,下一个自增值也是未知的。

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

InnoDB自增原理都搞不清楚,还怎么CRUD?相关推荐

  1. 中国股市:集合竞价的含义都搞不懂,还想知道主力出货还是洗盘?

    我最近在思考一个问题,关于心态和交易系统的调和,都说用系统的量化程度来解决心态问题,但是我发现成功的老手并不是多高的机械性操作.也可以说,这个市场不仅没有一个稳定利润的交易系统,而且手动的机械性买进卖 ...

  2. 把MySQL中的各种锁及其原理都画出来

    疫情期间在家工作时,同事使用了 insert into on duplicate key update 语句进行插入去重,但是在测试过程中发生了死锁现象: ERROR 1213 (40001): De ...

  3. 面试大厂被怼!这都搞不定,你只能做“搬运工”!

    每一个面试过大厂的程序员似乎总会有种种困境: 毕业季参加大厂校招面试,我本以为做过一些真实项目就不错了,没想到根本没问什么项目,都是基础知识,数学.算法,然而平时只喜欢学程序设计. 小公司工作3年,好 ...

  4. MySQL innoDB底层基础原理总结

    MySQL innoDB底层基础原理 前言:由于正在准备之后的实习面试,故总结了一部分MYSQL innoDB基础的问题,回答全为自己组织的语言,若有错各位大佬可及时指出,大家共同进步,谢谢. 1.i ...

  5. 面试大厂背怼!这都搞不定,你只能做“搬运工”!

    每一个面试过大厂的程序员似乎总会有种种困境: 毕业季参加大厂校招面试,我本以为做过一些真实项目就不错了,没想到根本没问什么项目,都是基础知识,数学.算法,然而平时只喜欢学程序设计. 小公司工作3年,好 ...

  6. MySQL内核月报 2015.01-MySQL · 捉虫动态· InnoDB自增列重复值问题

    问题重现 先从问题入手,重现下这个bug 这里我们关闭mysql,再启动mysql,然后再插入一条数据 我们看到插入了(2,2),而如果我没有重启,插入同样数据我们得到的应该是(4,2). 上面的测试 ...

  7. redis 什么是冷数据_阿里Java三面凉凉:微服务,Redis,JVM一个都搞不懂

    前言: 金九银十刚刚过去了,不知道很多小伙伴都拿到自己心仪的offer没有,我这边也收到了一个粉丝投来的消息,说看到阿里的面试真题之后人都是懵的,发现自己一窍不通,下面给大家分享我这个粉丝的经历,以及 ...

  8. 手机里竟然有这么多传感器!终于都搞懂了

    手机里竟然有这么多传感器!终于都搞懂了 本文来自快科技 随着技术的进步,手机已经不再是一个简单的通信工具,而是具有综合功能的便携式电子设备.手机的虚拟功能,比如交互.游戏.都是通过处理器强大的计算能力 ...

  9. [js] fetch和axios请求的原理都是基于XMLHttpRerequst吗?

    [js] fetch和axios请求的原理都是基于XMLHttpRerequst吗? axios是基于XMLHttpRequest的封装,而fetch是js原生支持的网络请求api,这在浏览器底层进行 ...

最新文章

  1. 一份忧伤的大厂生存百科
  2. UML系列图--用例图
  3. html em px的不同,CSS:区别 px、em、rem
  4. 【语法解释】init
  5. JQuery Datatables Dom 和 Language 参数详细说明
  6. scala 多线程_Scala中的多线程
  7. linux 命令缺失安装,Redhat7没有安装ifconfig命令的解决方法
  8. 计算机软考里面的英语试题,2011全国计算机软考网管英语试题及答案(4)
  9. MapReduce计算PMI
  10. android tv字体,android TV 屏幕适配 (一)
  11. linux dd从磁盘读取文件命令
  12. 怎么布置mysql数据库_MySQL数据库的安装,配置
  13. 分析FFMPEG中H264编码流程
  14. 企信下载的文件在哪里_苹果文件管理在哪里
  15. 纯js实现搜索框自动补全
  16. PS压缩1寸照片大小降低到50KB以下的方法
  17. 图解IFRS9 金融工具(8)减值准备规则比较
  18. Python的excel操作——PasteSpecial实现选择性粘贴自动化
  19. leaflet使用L.KML.js插件上传本地kml文件到leaflet中
  20. CentOS7系统之间设置共享文件夹

热门文章

  1. python dataframe 取每行的最大值,在python数据框中的每一行中查找最大值
  2. python for循环九九乘法表_python3:使用for循环打印九九乘法表
  3. abaqus分析用户手册单元卷_ABAQUS与你我的约定
  4. appium怎么操作物理返回键_这些Appium常用元素定位技巧,你掌握了几种?
  5. 2-4:套接字(Socket)编程之TCP通信
  6. windows IOCP模型
  7. docker0: iptables: No chain/target/match by that name.
  8. Redis为什么默认16个数据库,干什么用?
  9. 汇编语言:实验10 根据材料编程—2.解决除法溢出的问题
  10. jquery跨域Ajax请求