自增主键的连续

自增值(AUTO_INCREMENT):再向表插入数据的时候会生成该值,表示下一次需要自增值的插入数据操作所填充的自增值。

例:
table t { `id` int(11) AUTO_INCREMENT,`b` int(11) };
insert into t values(null,1)

此时自增值为2;

自增值的保存策略
myISAM:

保存于数据文件中。

InnoDB:

mysql5.7及以前会保存于内存中,每次重启打开会去找自增列的最大值,然后将这个值+1作为当前自增值。
重启会修改表的自增值,例:
假设当前表自增列最大值为10,自增值为11。删除自增列为10的之后重启,此时自增列最大值为9,自增值变为10。
mysql8.0+会将自增值保存在redo log中,重启不会变化。

自增值机制

1.如果插入数据的自增字段为0、null或者未指定,就将表当前自增值填入。
2.如果插入数据的自增字段有指定值X,会直接使用当前X插入,表自增值Y也会根据X而改变:
2.1 如果X<Y,那么表的自增值为Y。
2.2 如果X ≥ Y,就需要从auto_increment_offset以auto_increment_increment为步长,找到第一个大于X的值作为新的自增值。

执行流程:
  1. 执行器调用InnoDB引擎写入插入行。
  2. InnoDB检查没有指定自增值
    2.1 无自增值:
    将自增值改入数据行,修改表自增值。
    2.2 有自增值:
    执行下一步
  3. 执行插入操作,成功/报错。
批量申请自增值:

对于批量插入,Mysql有一个批量申请自增字段的策略:
1.语句执行过程中,第一次申请自增字段会分配一个自增值。
2.第二次申请会分配两个。
3.以此类推,同一个语句去多次申请自增值,每次申请到的数量是上一次的两倍。

自增不连续
  1. 唯一键冲突
    未指定自增字段数据,插入时产生了唯一键冲突,由于自增值修改优先执行于插入操作,自增值已修改但插入报错。
  2. 插入回滚
    未指定自增字段数据,在插入事务中进行回滚,自增值已修改但未插入。
  3. 批量申请造成的自增浪费
    未指定自增字段的多条数据,在同一语句内去申请填充自增字段,若申请的自增字段多于数据数量,那么将会出现自增字段浪费的情况。
自增不回退
自增回退:

并发执行时如果进行自增回退可能会造成键值冲突。(自增字段必须是唯一)
例如两个并行执行事务操作同一张表,事务A申请到自增值为2,事务B申请到自增值为3,此时表自增值为4。假设B提交成功但是A提交失败且允许自增值回退,那么回退后的表自增值为2。后续再次执行时申请自增值会申请到为3的值,就会产生唯一冲突。

解决方法:
  1. 每次申请之前先去判断是否存在,存在就跳过。
  2. 自增值得锁范围扩大至事务执行提交后,这样在事务完成后下个事务才可申请自增值。
问题:

由于解决方法影响性能,所以InnoDB不支持自增回退。

自增锁优化

在Mysql 5.1.22新增了innodb_autoinc_lock_mode参数,默认值为1。

  1. 为0时,采用Mysql5.0之前的策略,即语句执行结束后才释放锁。
  2. 为1时:
    2.1 insert语句,自增锁在申请之后就释放。
    2.2 类似insert…select这类的批量插入数据,自增锁要等语句结束才释放。
  3. 为2时,所有锁都是申请后就释放。
关于2.2的说明:

设置mysql自动递增值 mysql自动增长是什么意思_mysql


假设自增值申请结束立马释放自增锁,那么t2数据有可能为(1,1,1),(2,2,2),(4,3,3),(5,4,4),(3,5,5)。

假设binlog_format=statement,那么从库执行binlog时就可能会产生数据不一致。

解决办法:

1.原库批量插入时,固定生成连续id,自增锁知道语句执行结束才释放。
2.在binlog中把插入数据如实记录。设置innodb_autoinc_lock_mode为2,binlog_format为row。
相比于1,2提升并发性能问题解决了数据不一致的问题。