前言
如果一个线程回滚,例如唯一键冲突的情况回滚时,回滚了sql语句,但是并没有把自增的值也-1。那么就会导致下一条插入的数据自增id出现了跳跃。
自增主键为什么不是连续的?
- 前言
- 执行时机
- 为什么自增主键不是连续的
- 为什么不回滚自增主键
- innodb_autoinc_lock_mode
- 设置成2导致的问题
执行时机
mysql的自增主键+1的时机是在mysql真正执行之前,获取当前值并加1。
为什么自增主键不是连续的
如果一个线程回滚,例如唯一键冲突的情况回滚时,回滚了sql语句,但是并没有把自增的值也-1。那么就会导致下一条插入的数据自增id出现了跳跃。
为什么不回滚自增主键
之所以不让自增主键回滚是因为:
- 假如两个线程一个获取了id =2 ,另一个是id =3 。id=3的完成之后,如果id=2的需要回滚,那么后面再来一个插入请求,就会获取id=2的值,然后再来一个数据就会获取3从而导致主键冲突。
如果要解决这个冲突:- 就要每次插入时还要去数据库里查询一下是否有这个自增主键。这是非常耗时的,本来一个非常简单的操作,现在就会变得非常复杂。
- 还有一种方法就是扩大每次获取自增值的锁时间,对于自增值的锁一般是很快的获取之后+1然后就会释放。如果要防止上面的冲突,就是等这个sql执行完成之后,再允许其它线程获取这个锁。
这显然也会大大降低并发值,因此mysql放弃了连续。
innodb_autoinc_lock_mode
那么后来mysql添加了一个参数innodb_autoinc_lock_mode,
- 如果设置成0:就是会等一个写入执行完成之后,再让另一个线程获取自增的值
- 如果设置成1:那么对于单个的insert值,就会获取到自增值就直接释放。但是对于批量写入或者批量替换的请求,那么就会等这个操作执行完成之后,再让另一个线程获取自增值。
- 如果设置成2,那么所有的insert操作都会获取到自增值之后直接释放。不等sql执行结束。
设置成2导致的问题
如果再批量插入A的时候,id是自增1,2,3,4,6,7,另一个线程B也插入了一条数据5,
但是在写入binlog的时候,是一个线程一个线程写入的。
那么线程B插入的数据对应的id只能是线程A开头的或者结尾,如果binlog的format= statement。就会导致主备数据不一致。
所以这这又是一个将binlog的format= row的原因。
这里需要注意 如果线程A不是批量写入的语句。只是一个普通的insert语句ID 1,然后又有另一个线程B获取自增建ID 2。但是再写入binlog时,线程B先写入了,然后线程A写入。
这种情况下 即使binlog的format=statement 也不会导致主备数据的数据不一致。在binlog中还有一个步长的参数,在执行线程B时,步长会为2,ID还是2,执行线程A时,步长是1,ID还是1。