(尊重劳动成果,转载请注明出处:https://yangwenqiang.blog.csdn.net/article/details/91477092冷血之心的博客)
关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~
快速导航:
MySQL原理与实践(一):一条select语句引出Server层和存储引擎层
MySQL原理与实践(二):一条update语句引出MySQL日志系统
MySQL原理与实践(三):由三种数据结构引入MySQL索引及其特性
MySQL原理与实践(四):由数据库事务引出数据库隔离级别
MySQL原理与实践(五):数据库的锁机制
MySQL原理与实践(六):自增主键的使用
目录
前言:
正文:
自增主键:
自增值的保存策略:
自增值的修改机制:
自增主键为什么不连续?
自增主键的修改时机:
事务的回滚操作对于自增主键连续性的影响:
自增值为什么不可以回退?
总结:
自增id用完了怎么办?
总结:
在前面文章的介绍中,我们阐述了主键的重要性。我们通过普通索引查询时会利用主键做一个回表操作。当我们在建表的时候没有指定主键的时候,MySQL会自动帮我们创建一个rowid做为主键,由此可见主键的重要性。这篇博文中,我们主要分析总结自增主键的特性。
让我们来回忆下前边所介绍的主键相关特性。在MySQL原理与实践(三):由三种数据结构引入MySQL索引及其特性中介绍索引的时候,我给出了如下的两个索引树:
由于数据的无序插入会导致数据页的分裂,增加了索引的维护成本,所以我们建议尽量使用自增主键来保证插入数据的有序性。在阿里巴巴的Java开发手册中也有如下的要求:
这一条强制规定也说明了自增主键的重要性,在MySQL中我们使用auto_increment来标明某个字段是个自增长的主键。有如下的建表语句:
CREATE TABLE `t` (
`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;
创建的表t和其表结构如下:
在这个空表 t 里面执行 insert into t values(null, 1, 1); 插入一行数据,再执行 show create table 命令,结果如下:
可以看到,表定义里面出现了一个 AUTO_INCREMENT=2,表示下一次插入数据时,如果需要自动生成自增值,会生成 id=2。
不同的引擎对于自增值的保存策略不同,我们以MyISAM和InnoDB存储引擎来说明:
介绍了 MySQL 对自增值的保存策略以后,我们再看看自增值修改机制。
还是刚刚建立得表t,刚刚我们插入了一条(null,1,1)数据,查询select之后如下所示:
然后我们再插入一条(0,2,1)数据,查询结果如下:
然后我们不指主键id的值,插入了(3,1)数据,结果如下;
接着我们刻意指定了该主键id的值,插入了一条(5,5,1)数据,结果如下:
由上边的结果我们可以看出在 MySQL 里面,如果字段 id 被定义为 AUTO_INCREMENT,在插入一行数据的时候,自增值的行为如下:
接着,我们再次执行show create table t\G 查看当前的自增值:
可以看到,由于我们在上一条插入语句中直接指定了自增值为5,所以MySQL保存的自增值大小为6 。
有如下的规定:
根据要插入的值和当前自增值的大小关系,自增值的变更结果也会有所不同。假设,某次要插入的值是 X,当前的自增值是 Y。
接下来我们再次插入一条数据(null,5,1)因为列c上有唯一约束UNIQUE,所以会报错,如下所示:
然后,插入一条(null,6,1),看下结果:
可以看出,即使我们使用自增长的主键,并且默认步长为一,也会出现自增主键不连续的情况。那么为什么会出现这种情况呢?
假设,表 t 里面已经有了 (1,1,1) 这条记录,这时我再执行一条插入数据命令:
insert into t values(null, 1, 1);
这个语句的执行流程就是:
可以看到,这个表的自增值改成 3,是在真正执行插入数据的操作之前。这个语句真正执行的时候,因为碰到唯一键 c 冲突,所以 id=2 这一行并没有插入成功,但也没有将自增值再改回去。
所以,在这之后,再插入新的数据行时,拿到的自增 id 就是 3。也就是说,出现了自增主键不连续的情况。
我们再来看一下事务的回滚操作对于自增主键连续性的影响。有如下的语句:
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)
为什么在出现唯一键冲突或者回滚的时候,MySQL 没有把表 t 的自增值改回去呢?如果把表 t 的当前自增值从 3 改回 2,再插入新数据的时候,不就可以生成 id=2 的一行数据了吗?
我们来分析这个设计思路,看看自增值为什么不能回退?
假设有两个并行执行的事务,在申请自增值的时候,为了避免两个事务申请到相同的自增 id,肯定要加锁,然后顺序申请。自增 id 锁并不是一个事务锁,而是每次申请完就马上释放,以便允许别的事务再申请。
而为了解决这个主键冲突,有两种方法:
可见,这两个方法都会导致性能问题。造成这些麻烦的罪魁祸首,就是我们假设的这个“允许自增 id 回退”的前提导致的。
因此,InnoDB 放弃了这个设计,语句执行失败也不回退自增 id。也正是因为这样,所以才只保证了自增 id 是递增的,但不保证是连续的。
造成自增主键不连续的原因如下:
前面我们主要介绍了自增id的特点和重要性。每个自增 id 都是定义了初始值,然后不停地往上加步长。虽然自然数是没有上限的,但是在计算机里,只要定义了表示这个数的字节长度,那它就有上限。比如,无符号整型 (unsigned int) 是 4 个字节,上限就是2的32次方-1。
做为一个7*24小时提供服务的数据库,这个值是有可能被用完的,无非就是一个时间问题。那么自增id值用完会发生什么?我们以下列的语句来进行测试。
create table t(id int unsigned auto_increment primary key) auto_increment=4294967295;
insert into t values(null);
select * from t;
// 成功插入一行 4294967295
show create table t;
insert into t values(null);
//Duplicate entry '4294967295' for key 'PRIMARY'
结果如下:
我们可以看出,可以看到,第一个 insert 语句插入数据成功后,这个表的 AUTO_INCREMENT 没有改变(还是 4294967295),就导致了第二个 insert 语句又拿到相同的自增 id 值,再试图执行插入语句,报主键冲突错误。
4294967295不是一个特别大的数,对于一个频繁插入删除数据的表来说,是可能会被用完的。因此在建表的时候你需要考察你的表是否有可能达到这个上限,如果有可能,就应该创建成 8 个字节的 bigint unsigned。
表定义的自增值达到上限后的逻辑是:再申请下一个 id 时,得到的值保持不变。
文章的开头,我们说了如果你创建的 InnoDB 表没有指定主键,那么 InnoDB 会给你创建一个不可见的,长度为 6 个字节的 row_id。InnoDB 维护了一个全局的 dict_sys.row_id 值,所有无主键的 InnoDB 表,每插入一行数据,都将当前的 dict_sys.row_id 值作为要插入数据的 row_id,然后把 dict_sys.row_id 的值加 1。
实际上,在代码实现时 row_id 是一个长度为 8 字节的无符号长整型 (bigint unsigned)。但是,InnoDB 在设计时,给 row_id 留的只是 6 个字节的长度,这样写到数据表中时只放了最后 6 个字节,所以 row_id 能写到数据表中的值,就有两个特征:
也就是说,写入表的 row_id 是从 0 开始到 2的48次方-1。达到上限后,下一个值就是 0,然后继续循环。
当然,2的48次方-1 这个值本身已经很大了,但是如果一个 MySQL 实例跑得足够久的话,还是可能达到这个上限的。在 InnoDB 逻辑里,申请到 row_id=N 后,就将这行数据写入表中;如果表中已经存在 row_id=N 的行,新写入的行就会覆盖原有的行。
从这个角度看,我们还是应该在 InnoDB 表中主动创建自增主键。因为,表自增 id 到达上限后,再插入数据时报主键冲突错误,是更能被接受的。毕竟覆盖数据,就意味着数据丢失,影响的是数据可靠性;报主键冲突,是插入失败,影响的是可用性。而一般情况下,可靠性优先于可用性。
这篇文章中,我们介绍了自增主键的特性,包括自增主键为什么不是连续的,分析了唯一键冲突以及事务回滚都会导致其不连续。在文章的最后,还分析了主动创建的自增主键和InnoDB自动创建的自增主键rowid在自增值用完之后的表现,给出我们应该主动创建自增主键。
这篇文章是我们MySQL原理与实践的第六篇,如果有机会的话,我会继续更新MySQL原理与实践的相关总结与笔记,欢迎大家关注交流,希望对大家的学习有所帮助。
如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,可以进群366533258一起交流学习哦~
本群给大家提供一个学习交流的平台,内设菜鸟Java管理员一枚、精通算法的金牌讲师一枚、Android管理员一枚、蓝牙BlueTooth管理员一枚、Web前端管理一枚以及C#管理一枚。欢迎大家进来交流技术。
关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~