MySQL的自增主键是连续自增吗?( 二 )


【MySQL的自增主键是连续自增吗?】当 auto_increment_offset 和 auto_increment_increment 都是 1 的时候 , 新的自增值生成逻辑很简单,就是:

  • 如果准备插入的值 >= 当前自增值,新的自增值就是“准备插入的值 +1”;
  • 否则,自增值不变 。
3 自增值修改时机3.1 唯一键冲突假设表t有了存在(1,1,1)这条记录 , 再次执行一次数据命令:
insert into t values(null, 1, 1);这个语句的执行流程就是:
  • 执行器调用 InnoDB 引擎接口写入一行,传入的这一行的值是 (0,1,1);
  • InnoDB 发现用户没有指定自增 id 的值,获取表 t 当前的自增值 2;
  • 将传入的行的值改成 (2,1,1);
  • 将表的自增值改成 3;
  • 继续执行插入数据操作 , 由于已经存在 c=1 的记录,所以报 Duplicate key error,语句返回 。
可以看到,这个表的自增值修改为3之后也不会再回退,之后再插入拿到的自增id就是3,自增主键不再连续 。
3.2 事务回滚insert into t values(null,1,1);begin;insert into t values(null,2,2);rollback;insert into t values(null,2,2);//插入的行是(3,2,2)如上语句就会出现不连续自增id的情况 。MySQL不允许做回退,看如下的假设:假设有两个并行执行的事务,在申请自增值时 , 为了避免两个事务申请到相同自增id,肯定加锁,然后顺序申请 。
  • 假设事务A申请到了id=2,事务B申请到id=3 , 那么表t的自增值是4,之后继续执行;
  • 事务B正确提交后,事务A出现唯一键冲突;
  • 如果允许事务A把自增id回退,也就是表t当前自增值改回2;
  • 接下来继续执行的其他事务就会申请到id=2,然后再申请id=3 , 就会出现插入语句报错“主键冲突";
为了解决这个主键冲突,有两种方法:
  • 每次申请 id 之前,先判断表里面是否已经存在这个 id 。如果存在,就跳过这个 id 。但是,这个方法的成本很高 。因为,本来申请 id 是一个很快的操作,现在还要再去主键索引树上判断 id 是否存在 。
  • 把自增 id 的锁范围扩大 , 必须等到一个事务执行完成并提交,下一个事务才能再申请自增 id 。这个方法的问题,就是锁的粒度太大 , 系统并发能力大大下降 。
出于性能考虑,如果设计为必须连续,那就需要每次都去检查当前申请的ID是否已存在,浪费性能;或者提升锁粒度,会导致申请ID退化为串行申请 
3.3 批量申请自增id策略对于批量插入数据的语句,MySQL 有一个批量申请自增 id 的策略:
  • 语句执行过程中 , 第一次申请自增 id,会分配 1 个; 
  • 1 个用完以后,这个语句第二次申请自增 id , 会分配 2 个; 
  • 2 个用完以后,还是这个语句,第三次申请自增 id , 会分配 4 个; 
  • 依此类推,同一个语句去申请自增 id , 每次申请到的自增 id 个数都是上一次的两倍 。 
4 自增锁优化自增id锁并不是一个事务锁 , 而是每次申请完就马上释放,以便允许别的事务再申请 。
在MySQL 5.0版本的时候,自增锁的范围是语句级别 。也就是说,如果一个语句申请了一个表自增锁,这个锁会等语句执行结束以后才释放 。显然,这样设计会影响并发度 。
MySQL 5.1.22版本引入了一个新策略 , 新增参数innodb_autoinc_lock_mode,默认值是1 。
  • 这个参数的值被设置为 0 时,表示采用之前 MySQL 5.0 版本的策略,即语句执行结束后才释放锁;
  • 这个参数的值被设置为 1 时:
普通 insert 语句,自增锁在申请之后就马上释放;
类似 insert … select 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放;