(尊重劳动成果,转载请注明出处:https://yangwenqiang.blog.csdn.net/article/details/91477092冷血之心的博客)

关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~

快速导航:

MySQL原理与实践(一):一条select语句引出Server层和存储引擎层

MySQL原理与实践(二):一条update语句引出MySQL日志系统

MySQL原理与实践(三):由三种数据结构引入MySQL索引及其特性

MySQL原理与实践(四):由数据库事务引出数据库隔离级别

MySQL原理与实践(五):数据库的锁机制

MySQL原理与实践(六):自增主键的使用

目录

前言:

正文:

自增主键:

自增值的保存策略:

自增值的修改机制:

自增主键为什么不连续?

自增主键的修改时机:

事务的回滚操作对于自增主键连续性的影响:

自增值为什么不可以回退?

总结:

自增id用完了怎么办?

总结:


前言:

在前面文章的介绍中,我们阐述了主键的重要性。我们通过普通索引查询时会利用主键做一个回表操作。当我们在建表的时候没有指定主键的时候,MySQL会自动帮我们创建一个rowid做为主键,由此可见主键的重要性。这篇博文中,我们主要分析总结自增主键的特性。本文整理总结于极客时间 -《MySQL实战45讲》,欢迎大家订阅学习,干货满满。

正文:

自增主键:

让我们来回忆下前边所介绍的主键相关特性。在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存储引擎来说明:

  • MyISAM 引擎的自增值保存在数据文件中。
  • InnoDB 引擎的自增值,其实是保存在了内存里,并且到了 MySQL 8.0 版本后,才有了“自增值持久化”的能力,也就是才实现了“如果发生重启,表的自增值可以恢复为 MySQL 重启前的值”,具体情况是:
    • 在 MySQL 5.7 及之前的版本,自增值保存在内存里,并没有持久化。每次重启后,第一次打开表的时候,都会去找自增值的最大值 max(id),然后将 max(id)+1 作为这个表当前的自增值。
      举例来说,如果一个表当前数据行里最大的 id 是 10,AUTO_INCREMENT=11。这时候,我们删除 id=10 的行,AUTO_INCREMENT 还是 11。但如果马上重启实例,重启后这个表的 AUTO_INCREMENT 就会变成 10。
      也就是说,MySQL 重启可能会修改一个表的 AUTO_INCREMENT 的值。
    • 在 MySQL 8.0 版本,将自增值的变更记录在了 redo log 中,重启的时候依靠 redo log 恢复重启之前的值。

介绍了 MySQL 对自增值的保存策略以后,我们再看看自增值修改机制。

自增值的修改机制:

还是刚刚建立得表t,刚刚我们插入了一条(null,1,1)数据,查询select之后如下所示:

然后我们再插入一条(0,2,1)数据,查询结果如下:

然后我们不指主键id的值,插入了(3,1)数据,结果如下;

接着我们刻意指定了该主键id的值,插入了一条(5,5,1)数据,结果如下:

由上边的结果我们可以看出在 MySQL 里面,如果字段 id 被定义为 AUTO_INCREMENT,在插入一行数据的时候,自增值的行为如下:

  • 如果插入数据时 id 字段指定为 0、null 或未指定值,那么就把这个表当前的 AUTO_INCREMENT 值填到自增字段
  • 如果插入数据时 id 字段指定了具体的值,就直接使用语句里指定的值。

接着,我们再次执行show create table t\G 查看当前的自增值:

可以看到,由于我们在上一条插入语句中直接指定了自增值为5,所以MySQL保存的自增值大小为6 。

有如下的规定:

根据要插入的值和当前自增值的大小关系,自增值的变更结果也会有所不同。假设,某次要插入的值是 X,当前的自增值是 Y。

  • 如果 X<Y,那么这个表的自增值不变
  • 如果 X≥Y,就需要把当前自增值修改为新的自增值。

接下来我们再次插入一条数据(null,5,1)因为列c上有唯一约束UNIQUE,所以会报错,如下所示:

然后,插入一条(null,6,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,是在真正执行插入数据的操作之前。这个语句真正执行的时候,因为碰到唯一键 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 锁并不是一个事务锁,而是每次申请完就马上释放,以便允许别的事务再申请。

  1. 假设事务 A 申请到了 id=2, 事务 B 申请到 id=3,那么这时候表 t 的自增值是 4,之后继续执行
  2. 事务 B 正确提交了,但事务 A 出现了唯一键冲突
  3. 如果允许事务 A 把自增 id 回退,也就是把表 t 的当前自增值改回 2,那么就会出现这样的情况:表里面已经有 id=3 的行,而当前的自增 id 值是 2
  4. 继续执行的其他事务就会申请到 id=2,然后再申请到 id=3。这时,就会出现插入语句报错“主键冲突”。

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

  1. 每次申请 id 之前,先判断表里面是否已经存在这个 id。如果存在,就跳过这个 id。但是,这个方法的成本很高。因为,本来申请 id 是一个很快的操作,现在还要再去主键索引树上判断 id 是否存在
  2. 把自增 id 的锁范围扩大,必须等到一个事务执行完成并提交,下一个事务才能再申请自增 id。这个方法的问题,就是锁的粒度太大,系统并发能力大大下降。

可见,这两个方法都会导致性能问题。造成这些麻烦的罪魁祸首,就是我们假设的这个“允许自增 id 回退”的前提导致的。

因此,InnoDB 放弃了这个设计,语句执行失败也不回退自增 id。也正是因为这样,所以才只保证了自增 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
  • 当 dict_sys.row_id=2的48次方时,如果再有插入数据的行为要来申请 row_id,拿到以后再取最后 6 个字节的话就是 0。

也就是说,写入表的 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#管理一枚。欢迎大家进来交流技术。

关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~

MySQL原理与实践(六):自增主键的使用相关推荐

  1. 七种MYSQL插入数据后返回自增主键ID的方法

    我们都知道,mysql中的insert插入之后会有返回值,返回的是影响的行数,也就是说,成功插入一条数据之后返回的是1,失败则返回0.那么,很多时候我们都想要得到最后插入的id值,下面七种方法均可,结 ...

  2. MySql基础篇---003 SQL之DDL、DML、DCL使用篇:创建和管理表 ,数据处理之增删改,MySQL数据类型精讲 ,约束:联合主键

    第10章_创建和管理表 讲师:尚硅谷-宋红康(江湖人称:康师傅) 官网:http://www.atguigu.com 1. 基础知识 1.1 一条数据存储的过程 存储数据是处理数据的第一步.只有正确地 ...

  3. mysql 主键自增语句_MySQL 自增主键

    以下仅考虑 InnoDB 存储引擎. 自增主键有两个性质需要考虑: 单调性 每次插入一条数据,其 ID 都是比上一条插入的数据的 ID 大,就算上一条数据被删除. 连续性 插入成功时,其数据的 ID ...

  4. MYSQL自增主键ID重置

    MYSQL在创建一个带有自增主键ID的表时,通常在删除数据时,导致自增主键不连续了.使用下面的SQL脚本可以重置主键. -- 1.重置已有数据主键 SET @rownum = 0; UPDATE ta ...

  5. 你真的懂自增主键(auto_increment)?

       自增主键是我们在设计数据库表结构时经常使用的主键生成策略,主键的生成可以完全依赖数据库,无需人为干预,在新增数据的时候,我们只需要将主键设置为null,0或者不设置该字段,数据库就会为我们自动生 ...

  6. MySQL自增主键auto_increment原理 与 自增主键出现间隙不连续现象的定位

    一.背景: 1.1.业务描述与SQL: 为了保存机器上报信息(业务需求是每个机器只需保存最新的一条记录),原 SQL 语句如下(其中,machineId 的为唯一索引,t_report_pad 的 i ...

  7. 为什么 MySQL 的自增主键不单调也不连续

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 转自:真没什么逻辑/Draveness 当我们在使用关系型数据库时 ...

  8. MySQL自增主键一定是连续的吗

    测试环境: MySQL版本:8.0 数据库表:T (主键id,唯一索引c,普通字段d) 如果你的业务设计依赖于自增主键的连续性,这个设计假设自增主键是连续的.但实际上,这样的假设是错的,因为自增主键不 ...

  9. 无法去掉自增标识_为什么 MySQL 的自增主键不单调也不连续

    为什么这么设计(Why's THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点.对具体实现造成的影响 ...

  10. mysql自增主键到头了怎么办_自增主键用完了怎么办

    在面试中,大家应该经历过如下场景 面试官:"用过mysql吧,你们是用自增主键还是UUID?" 你:"用的是自增主键" 面试官:"为什么是自增主键?& ...

最新文章

  1. Docker持续部署图文详解
  2. 分析USB平台设备模型框架(1)
  3. jsf项目启动报:java.lang.ClassNotFoundException: javax.faces.webapp.FacesServlet
  4. 编译linux内核预备,Linux内核预备知识(1)
  5. 腾讯实习笔试:关于几个有序数组求交集的问题
  6. ASP.NET MVC中使用DropDownList
  7. 获取10~99(包含10和99)的“总和”与“偶数”的个数
  8. x86汇编代码转x64平台使用(VS2010测试通过)最简单的方法
  9. JavaScript面向对象详解
  10. yamdi 实现添加元数据的注入flv文件,实现Nginx搭建flv视频浏览器上点播拖拽
  11. redhat5.4上安装oracle9i
  12. 打印机服务器启用后自动关闭,Win7打印机服务自动关闭了怎么打开|Win7打开打印机服务的简单方法...
  13. C语言指针详解(通俗易懂)
  14. 技术分解:光纤传感在交通监控中的应用
  15. 计算机里边的单位换算:b、KB、MB、GB、TB等
  16. “知识地图”助员工岗位成才
  17. 八爪鱼爬虫采集天猫商品数据教程
  18. Arduino PS2摇杆
  19. Win7系统连接服务器经常掉线,win7网络经常掉线怎么办 win7网络不稳定如何解决...
  20. 使用QGIS实现城市空气质量指数(AQI)数据可视化

热门文章

  1. 第三方登录—QQ登录
  2. @synthesize 和 @dynamic 分别表示什么
  3. 设置swiper中的高度
  4. openwrt_ipsec_racoon.init 分析
  5. ES7.16.2基础操作之slop查询(三)
  6. 一个猫下面的计算机互相访问,光猫连接路由器和电脑如何互相访问
  7. 联想e570c固态接口支持协议_thinkpad e570c15.6英寸笔记本电脑支持什么固态硬盘接口...
  8. mysql failover_MySQL 8.0.22 新特性Async Replication Auto failover
  9. 2018-2019-1 20165309 20165312 20165330 实验一 开发环境的熟悉
  10. smbcontrol - 向smbd或nmbd进程发送消息