前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MySQL自增主键为什么不连续

MySQL自增主键为什么不连续

作者头像
shysh95
发布2022-04-07 19:33:46
8.3K0
发布2022-04-07 19:33:46
举报
文章被收录于专栏:shysh95shysh95
代码语言:javascript
复制
CREATE TABLE `t` (
  `id` int NOT NULL AUTO_INCREMENT,
  `c` int DEFAULT NULL,
  `d`  int DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `c` (`c`)
) ENGINE=InnoDB;

自增主键好处?

自增主键可以让主键索引尽量的保持递增顺序插入,避免页分裂,索引更加紧凑。

自增主键保存在何处?

代码语言:javascript
复制
insert into t values(null, 1, 1);
show create table t\G

可以看到表定义中出现了AUTO_INCREMENT=2,表示下一次插入数据时如果需要自动生成自增值,那么id便是2。

不同的引擎对于自增值的保存策略不同:

  • MyISAM引擎的自增值保存在数据文件中
  • InnoDB引擎的自增值保存在内存里,但是在MySQL8.0以后,该自增值才可以被持久化:MySQL5.7以前,自增值没有持久化每次重启后第一次打开表的时候,会找自增值的最大值max(id),然后将最大值加1作为这个表的自增值;MySQL8.0版本会将自增值的变更记录在redo log中,重启时依靠redo log恢复。

自增值的修改机制

自增值的修改行为如下:

  1. 如果插入数据时id字段指定为0、null或者未指定值,那么就把该表的AUTO_INCREMENT值填到自增字段
  2. 如果插入数据时id字段指定了具体的值,就直接使用语句里指定的值

自增值的变更情况如下,假设某次要插入的值是X,当前的自增值是Y:

  1. 如果X<Y,表的自增值不变
  2. 如果X>=Y,需要把当前自增值修改为新的自增值

新的自增值生成算法

从auto_increment_offset(默认值是1)开始,以auto_increment_increment(默认值是1)为步长,持续叠加,直到找到第一个大于X的值,作为新的自增值

唯一键冲突导致自增主键不连续

代码语言:javascript
复制
insert into t values(null, 1, 1);
  1. 执行器调用InnoDB引擎写入一行,传入的这一行的值是(0,1,1)
  2. InnoDB发现用户没有指定id,获取表t当前的自增值2
  3. 将传入的行改为(2,1,1)
  4. 将表的自增值改为1
  5. 继续执行插入数据操作,但是由于c=1的记录已经存在,所以会返回Duplicat key error,语句返回

上述执行过程可以看出,自增值的修改是在真正插入数据的操作之前,如果数据真正插入的时候异常,也不会将自增值改回去。

事务回滚导致自增主键不连续

代码语言:javascript
复制
set autocommit=0;
begin;
insert into t values(null, 2, 2);
rollback;
show create table t\G

可以看到自增主键已经从3变成4,但是并没有因为事务的回滚回退。

事务回滚为什么自增值不能回退

两个并行的事务在申请自增值的时候,为了避免两个事务申请到相同的自增id,需要加锁按照顺序申请,如果自增值可以回退需要做一些特殊处理:

  1. 每次申请id之前,判断表里此id是否存在(去主键索引树上判断该id是否存在)
  2. 把自增id的锁范围扩大,必须等到一个事务提交后才,下一个事务才可以申请id,锁粒度太大,系统并发能力极大下降

为了避免上述的性能消耗,InnoDB即使语句执行失败也不回退自增id。

批量插入导致自增值不连续

自增值锁不是一个事务锁,每次申请完就释放,方便其他事务获取自增值。

参数innodb_autoinc_lock_mode的不同会影响锁的释放时机:

  1. 该参数如果为0,语句执行结束后释放锁
  2. 设置为1:普通insert语句,自增锁在申请后马上释放;insert...select这样的批量插入语句等语句结束后才释放
  3. 设置为2:申请后就释放锁
代码语言:javascript
复制
show global variables like 'innodb_autoinc_lock_mode';

如果innodb_autoinc_lock_mode值设置为1,对于insert...select这种无法提前知道申请多少个id,那就在需要的时候申请1个,如果有10w行数据那就需要申请10万次,对于批量插入数据的语句,MySQL有一个批量申请自增id的策略:

  1. 语句执行过程中,第一次申请自增id,分配1个
  2. 1个用完以后,第二次申请,会分配2个
  3. 2个用完以后,第三次申请,会分配4个
  4. 依此类推,每次申请都是上一次的两倍(最后一次申请不一定全部使用)

在innodb_autoinc_lock_mode参数为1的情况下,如果大批量插入数据也会造成自增id的不连续。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-03-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员修炼笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档