mysql 5.1.22引进innodb_autoinc_lock_mode参数,优化了自增锁的性能,提高了MySQL使用的便捷性和可配置性,用户可以结合业务场景合理设置自增锁参数,更方便便捷;
1、innodb_autoinc_lock_mode=0:
In this lock mode, all “INSERT-like” statements obtain a special table-level AUTO-INC lock for inserts into tables with AUTO_INCREMENT columns. This lock is normally held to the end of the statement (not to the end of the transaction) to ensure that auto-increment values are assigned in a predictable and repeatable order for a given sequence of INSERT statements, and to ensure that auto-increment values assigned by any given statement are consecutive.
在这种锁模式中,所有“类似INSERT”的语句都会获得一个特殊的表级AUTO-INC锁,用于插入到具有AUTO_INCREMENT列的表中。此锁通常保持到语句的末尾(而不是事务的末尾),以确保自动增量值以可预测和可重复的顺序分配给给定的INSERT语句序列,并确保任何给定语句分配的自动增量值是连续的。
总结:innodb_autoinc_lock_mode=0的时候:
1)自增锁的范围是语句级别,表级AUTO-INC锁等语句执行完毕才释放;
2)尽量保证自增值在大部分情况下是顺序的;
3)会影响并发度;
4)针对insert的语句,可以保证基于statement的binlog格式下的主从同步一致性,具体原因看后面解释;
2、innodb_autoinc_lock_mode=1:
1)普通 insert 语句,自增锁在申请之后就马上释放;无法保证保证自增值是顺序的;
2)类似 insert … select 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放;可以保证基于statement的binlog格式下的主从同步一致性
3)提高了并发度,但是为了保证一致性, insert … select 这样的批量插入数据的语句依旧是语句执行完毕才释放自增锁;
3、innodb_autoinc_lock_mode=2
1)参数
innodb_autoinc_lock_mode=2时,所有的申请自增主键的动作都是申请后就释放锁。
2)无法保证基于statement的binlog格式下的主从同步一致性,但是row格式binlog可以保证一致性!
3)申请后就释放锁,所以并发性能最好;
4、为什么MySQL默认
innodb_autoinc_lock_mode=1?
为什么
innodb_autoinc_lock_mode=1时,insert … select 要使用语句级的锁?为什么这个参数的默认值不是 2?
答案是,
这么设计还是为了数据的一致性。
我们一起来看一下这个场景:
图 4 批量插入数据的自增锁
在这个例子里,我往表 t1 中插入了 4 行数据,然后创建了一个相同结构的表 t2(自增开始值为1),然后两个 session 同时执行向表 t2 中插入数据的操作。
你可以设想一下,如果 session B 是申请了自增值以后马上就释放自增锁,那么就可能出现这样的情况:
session B 先插入了两个记录,(1,1,1)、(2,2,2);
然后,session A 来申请自增 id 得到 id=3,插入了(3,5,5);
之后,session B 继续执行,插入两条记录 (4,3,3)、 (5,4,4)。
你可能会说,这也没关系吧,毕竟 session B 的语义本身就没有要求表 t2 的所有行的数据都跟 session A 相同。
是的,从数据逻辑上看是对的。但是,如果我们现在的 binlog_format=statement,你可以设想下,binlog 会怎么记录呢?
由于两个 session 是同时执行插入数据命令的,所以 binlog 里面对表 t2 的更新日志只有两种情况:要么先记 session A 的,要么先记 session B 的。
但不论是哪一种,这个 binlog 拿去从库执行,或者用来恢复临时实例,备库和临时实例里面,session B 这个语句执行出来,生成的结果里面,id 都是连续的。这时,这个库就发生了数据不一致。
你可以分析一下,出现这个问题的原因是什么?
其实,这是因为原库 session B 的 insert 语句,生成的 id 不连续。这个不连续的 id,用 statement 格式的 binlog 来串行执行,是执行不出来的。
而要解决这个问题,有两种思路:
一种思路是,让原库的批量插入数据语句,固定生成连续的 id 值。所以,自增锁直到语句执行结束才释放,就是为了达到这个目的。
另一种思路是,在 binlog 里面把插入数据的操作都如实记录进来,到备库执行的时候,不再依赖于自增主键去生成。这种情况,其实就是 innodb_autoinc_lock_mode 设置为 2,同时 binlog_format 设置为 row。
因此,在生产上,尤其是有 insert … select 这种批量插入数据的场景时,从并发插入数据性能的角度考虑,我建议你这样设置:innodb_autoinc_lock_mode=2 ,并且 binlog_format=row. 这样做,既能提升并发性,又不会出现数据一致性问题。
需要注意的是,我这里说的批量插入数据,包含的语句类型是 insert … select、replace … select 和 load data 语句。
但是,在普通的 insert 语句里面包含多个 value 值的情况下,即使 innodb_autoinc_lock_mode 设置为 1,也不会等语句执行完成才释放锁。因为这类语句在申请自增 id 的时候,是可以精确计算出需要多少个 id 的,然后一次性申请,申请完成后锁就可以释放了。
也就是说,批量插入数据的语句,之所以需要这么设置,是因为“不知道要预先申请多少个 id”。
5、MySQL针对一个sql申请多个自增id的优化
既然预先不知道要申请多少个自增 id,那么一种直接的想法就是需要一个时申请一个。但如果一个 select … insert 语句要插入 10 万行数据,按照这个逻辑的话就要申请 10 万次。显然,这种申请自增 id 的策略,在大批量插入数据的情况下,不但速度慢,还会影响并发插入的性能。
因此,对于批量插入数据的语句,MySQL 有一个批量申请自增 id 的策略:
语句执行过程中,第一次申请自增 id,会分配 1 个;
1 个用完以后,这个语句第二次申请自增 id,会分配 2 个;
2 个用完以后,还是这个语句,第三次申请自增 id,会分配 4 个;
依此类推,同一个语句去申请自增 id,每次申请到的自增 id 个数都是上一次的两倍。
举个例子,我们一起看看下面的这个语句序列:
insert into t values(null, 1,1);
insert into t values(null, 2,2);
insert into t values(null, 3,3);
insert into t values(null, 4,4);
create table t2 like t;
insert into t2(c,d) select c,d from t;
insert into t2 values(null, 5,5);
insert…select,实际上往表 t2 中插入了 4 行数据。但是,这四行数据是分三次申请的自增 id,第一次申请到了 id=1,第二次被分配了 id=2 和 id=3, 第三次被分配到 id=4 到 id=7。
由于这条语句实际只用上了 4 个 id,所以 id=5 到 id=7 就被浪费掉了。之后,再执行 insert into t2 values(null, 5,5),实际上插入的数据就是(8,5,5)。
如下所示:
这是主键 id 出现自增 id 不连续的原因。
6、综上所述:
1、建议生产模式配置innodb_autoinc_lock_mode=2 ,并且 binlog_format=row. 这样做,既能提升并发性,又不会出现数据一致性问题;
2、首先我们有一个共识,mysql 5.7版本中无法绝对保证自增id连续,因为当事务回滚的时候自增id不会回滚、申请多个id的时候优化策略等原因,所以如果想要尽量保证自增id连续性,可以设置innodb_autoinc_lock_mode=1 ,并且 binlog_format=row; 这样做,既能尽量保证连续性,又不会出现数据一致性问题;