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


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

文章插图
以下文章来源于苦味代码  , 作者L
虽然我们习惯于给主键ID指定AUTO_INCREMENT属性 , 但是AUTO_INCREMENT也是可以指定到非主键字段的 , 唯一的约束就是这个字段上面得加索引 , 有了索引 , 就可以通过类似SELECT MAX(*ai_col*)的语句快速读到这列数据的最大值 。
本文要探讨的话题是MySQL的InnoDB引擎处理自增数据列的原理
MySql 5.1之前的实现【InnoDB自增原理都搞不清楚,还怎么CRUD?】在这个版本之前 , 用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执行的流程为:
全表加 AUTO-INC锁
1.1 生成主键ID:101
1.2 将行(101, 'a')插入表中
1.3 生成主键ID: 102
1.4 将行(102, 'b')插入表中
...
释放 AUTO-INC锁
MySql5.1之前的这种实现方式可以保证AUTO_INCREMENT严格自增 , 但是并发程度也最差 , 因为AUTO_INCREMENT锁是全表加锁直到这条语句结束
MySql 5.1版本带来的优化前文中的insert语句是比较简单的 , 所谓简单的insert语句指的是插入的的数据行数是可以提前确定的 , 与之相对的是Bulk insert比如INSERT ... SELECT这类语句 , 这类插入语句的插入行数不能提前确定 。
在这个版本以及之后 , 对于简单语句的插入 , 不再加全表的AUTO-INC锁 , 只会在产生自增列数据的时候加一个轻量级的互斥锁 , 等自增数据分配好 , 锁就释放了 , 因此像上面的例子 , 在MySql5.1之后的执行流程如下
加轻量级互斥锁
1.1 分配自增数据
释放锁
将行(101, 'a')插入表中
将行(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锁 , 主要是基于执行语句的主从复制要能在从库完全回放复制主库 , 所有的语句执行结果就不能和执行顺序有关 。


推荐阅读