39 自增主键为何不连续

39 自增主键为何不连续

Mysqlinnodb的自增主键,因为自增主键可让主键索引尽可能得保持递增顺序插入,避免了页分裂,所以索引更紧凑。mysql

在设计的时候,自增主键是不能保证连续的。算法

| t39   | CREATE TABLE `t39` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `c` (`c`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8

自增值保存在什么地方

上表插入一行值sql

(system@127.0.0.1:3306) [test]> insert into t39 values(null,1,1); 在执行show create tables | t39   | CREATE TABLE `t39` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `c` (`c`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 |

看到AUTO_INCREMENT=2,表示下一次插入数据时,若是须要自动生成自增值,会生成id=2并发

实际上,表的结构定义存放在后缀名为.frm的文件中,可是并不会保存自增值性能

不一样的引擎对于自增值的报错策略不一样优化

--myisam引擎的自增值保存在数据文件中spa

--innodb引擎的自增值,实际上是保存在内存里,而且到了mysql8.0版本后,才有了”自增值持久化”的能力,若是发生重启,表的自增值能够恢复到mysql重启前的值设计

---mysql5.7及以前的版本,自增值保存在内存中,并没有持久化,每次重启,第一次打开表的时候,都会去找自增值的最大值max(id),而后将max(id)+1做为这个表当前自增的值。code

---mysql8.0版本后,将自增值的变动记录在了redo log,重启的时候依靠redo log恢复重启以前的值。orm

自增值修改机制

mysql里面,若是字段id被定义为auto_increment,在插一行数据的时候,自增值的行为以下:

--1 若是插入数据时id字段定义为0null或未指定值,那么就把这个表当前auto_increment的值填到自增字段。

--2若是插入数据是id字段指定了具体的值,就直接使用语句指定的值。

根据要插入的值和当前自增值的大小关系,自增值的变动结果也会有所不一样,假设,某次要插入的值是x,当前自增的值是y

---1 x<y,那么这个表的自增值不变

---2 x>=y,就须要把当前自增值修改成新的自增值。

新的自增值生成算法是:从auto_increment_offset开始,以auto_increment_increment为步长,持续叠加,直到找到第一个大于x的值,做为新的自增值

自增值的修改时机

假设表t39已经有一条记录(1,1,1),咱们在执行一个insert

(system@127.0.0.1:3306) [test]> insert into t39 values(null,1,1); ERROR 1062 (23000): Duplicate entry '1' for key 'c'

这个语句的执行流程:

--1执行器调用innodb引擎接口写入一行,传入的这一行的值是(0,1,1)

--2 innodb发现用户没有指定自增id的值,获取表t39当前的自增值是2

--3 将传入的行的值改为(2,1,1,)

--4 将表的自增值改为3

--5继续执行插入数据操做,因为已经存在c=1,因此报错duplicate

能够看到,这个表的自增值改为了3,是在真正执行插入操做的时候,可是这个语句因为报错,id=2的这一行并无插入成功,但也没有将自增值改回去

当咱们在插入一行的时候,自增值变成了3

能够看到,这个操做序列复现了一个自增主键id不连续的现场,可见,惟一键冲突是致使自增主键id不连续的一个缘由

一样的,事务回滚也会产生相似的现象。

insert into t39 values(null,1,1); begin; insert into t39 values(null,2,2); rollback; insert into t39 values(null,2,2); // 插入的行是 (3,2,2)

其实,mysql不把自增是回退,是为了提高性能

假设有两个并行执行的事务,在申请自增值的时候,为了不两个事务申请到相同的自增id,确定是要加锁,而后顺序申请

--1 加锁事务a申请到了id=2,事务b申请到了id=3,这时候表t的自增是4,以后继续执行

--2 事务b正确提交了,可是事务a出现了惟一性冲突

--3 若是容许事务a把自增id退回,也就是把表t当前的自增值改回2,那么表里的状况,表里已经有了id=3的行,而当前的自增id2

--4 接下来,继续执行的其余事务就会申请到id=2,而后在申请到id=3时,就会报错主键冲突。

而为了解决这个主键冲突,有两种办法

--1 每次申请id以前,先判断表里面是否已经存在这个id,若是存在,就跳过这个id,但这个方法成本很高,要去主键索引树上判断id是否存在

--2 把自增id的锁范围扩大,必须等到一个事务执行完并提交,下一个事务才能申请id,这个锁的粒度太大,系统并发能力大大降低。

因此innodb的自增id保证了递增,但不保证是连续

自增锁的优化

Mysql参数innodb_autoinc_lock_mode,默认是1

--1 这个参数为0是,表示采用以前的mysql 5.0版本的设计,即语句执行结束后才释放锁

--2 这个参数位1

---普通的insert语句,自增锁在申请后就立马释放

---相似insert。。。Select这样批量插入,自增锁仍是要等语句结束后才被释放

--3 参数为2时,全部的申请自增主键的动做都是申请后就释放

SESSION A

SESSION B

> insert into t39 values(null,1,1);

> insert into t39 values(null,2,2);

> insert into t39 values(null,3,3);

> insert into t39 values(null,4,4);

 

 

> create table t39_1 like t39;

> insert into t39 values(null,5,5);

> insert into t39_1(c,d) select c,d from t39;

--1 在原库的批量插入数据语句,固定生成连续的id值,因此,自增锁直到语句执行结束后才释放,

--2 binlog里面把数据的操做都记录下来,到备库去执行的时候,再也不依赖于自增主键去生成,其实就是innodb_autoinc_lock_mode=2,binlog_format=row

所以,在生产上,尤为是有insert。。。Select这种批量插入数据的场景,但并发插入数据性能考虑,建议设置innodb_autoinc_lock_mode=2,binlog_format=row

须要注意的是,批量插入,包含的语句类型为insert。。。Selectreplace。。。Selectload data语句,是由于不知道预先申请多少个id

对于批量插入数据的语句,mysql有一个批量申请自增id的策略

--1 语句执行过程当中,第一次申请自增id,会分配1

--2 1个用完之后,这个语句第二次申请自增id,会分配2

--3 2个用完之后,仍是这个语句,第三次申请自增id,会分配4

--4 依次类推,同一个语句去申请自增id每次申请到的自增id个数都是上一次的两倍

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);

Inset。。。Select,实际上往表t2中插入了4行数据,但,这4行数据是分三次申请的自增id,第一次申请到id=1,第二次被分配了id=2id=3,第三次被分配了id=4id=7,因为这个语句实际上只有了4id,因此id=57就浪费了。

最后执行insert into t2 values(null, 5,5);,实际上插入的数据是(8,5,5)

也是主键id出现自增id不连续的第三种缘由。

相关文章
相关标签/搜索