【MySQL的自增主键是连续自增吗?】当 auto_increment_offset 和 auto_increment_increment 都是 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.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 。这个方法的问题,就是锁的粒度太大 , 系统并发能力大大下降 。
3.3 批量申请自增id策略对于批量插入数据的语句,MySQL 有一个批量申请自增 id 的策略:
- 语句执行过程中 , 第一次申请自增 id,会分配 1 个;
- 1 个用完以后,这个语句第二次申请自增 id , 会分配 2 个;
- 2 个用完以后,还是这个语句,第三次申请自增 id , 会分配 4 个;
- 依此类推,同一个语句去申请自增 id , 每次申请到的自增 id 个数都是上一次的两倍 。
在MySQL 5.0版本的时候,自增锁的范围是语句级别 。也就是说,如果一个语句申请了一个表自增锁,这个锁会等语句执行结束以后才释放 。显然,这样设计会影响并发度 。
MySQL 5.1.22版本引入了一个新策略 , 新增参数innodb_autoinc_lock_mode,默认值是1 。
- 这个参数的值被设置为 0 时,表示采用之前 MySQL 5.0 版本的策略,即语句执行结束后才释放锁;
- 这个参数的值被设置为 1 时:
类似 insert … select 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放;