自增主键为什么不是连续的?
39 | 自增主键为什么不是连续的?
自增主键保存在哪里?
自增主键并不保存在表结构定义的frm文件中。
- MyISAM 引擎自增值保存在数据文件中
- MySQL 8.0之前,自增值保存在内存中,重启后设置为max(id) + 1; MySQL 8.0之后,自增长值保存在了redo log中,重启的时候依靠redo log 恢复重启之前的值。
哪些情况会造成自增主键不连续
- 事务回滚
- 插入语句执行失败,比如唯一键冲突
- insert ... into select * from 批量插入时,执行失败。
- 指定了id插入,id比当前数据库内的大的多
auto_increment_offset 和 auto_increment_increment 是两个系统参数,分别用来表示自增的初始值和步长,默认值都是 1。
参数auto_increment_offset控制自增ID增加的步长。 双M结构会用到,比如设置另一个库auto_increment_offset=2, 让一个库的自增ID为奇数,另一个为偶数,避免两个库生成的主键发生冲突。
参数innodb_autoinc_lock_mode
默认值是1. 申请自增id需要加锁,根据设置参数的不同,加锁的程度不同
- 0,表示语句执行结束后才释放锁。此时锁是语句级别的
- 1,普通insert语句,自增锁申请后马上释放。insert...select 这样的批量插入语句,自增锁要等语句结束后才被释放。
- 2,申请自增主键的动作申请后就释放锁。
insert...select 必须加语句级别的锁,因为insert..select 中有可能有其他线程也来申请自增id,导致insert..select 插入语句的id是不连续的。
在生产上,尤其是有 insert … select 这种批量插入数据的场景时,从并发插入数据性能的角度考虑,我建议你这样设置:innodb_autoinc_lock_mode=2 ,并且 binlog_format=row. 这样做,既能提升并发性,又不会出现数据一致性问题。
如果insert语句是多个value的情况下,一定会计算出需要多少自增id,然后再释放自增锁。
对于需要批量插入数据的语句,MySQL 批量申请自增ID策略。 刚开始申请1,后面申请数量是上一次的倍数。
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)。