详解MySQL自增主键的实现

 更新时间:2021年09月05日 15:55:54   作者:邋遢的流浪剑客  
现在大部分的软件开发都离不开数据库。而mysql也是经常会用到的一个数据库。mysql数据库中有一个主键生成规则,就是自增。也是我们经常会用到的。本文就来介绍一下

一、自增值保存在哪儿?

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

1.MyISAM引擎的自增值保存在数据文件中

2.InnoDB引擎的自增值,在MySQL5.7及之前的版本,自增值保存在内存里,并没有持久化。每次重启后,第一次打开表的时候,都会去找自增值的最大值max(id),然后将max(id)+步长作为这个表当前的自增值

select max(ai_col) from table_name for update;

在MySQL8.0版本,将自增值的变更记录在了redo log中,重启的时候依靠redo log恢复重启之前的值

二、自增值修改机制

如果字段id被定义为AUTO_INCREMENT,在插入一行数据的时候,自增值的行为如下:

1.如果插入数据时id字段指定为0、null或未指定值,那么就把这个表当前的AUTO_INCREMENT值填到自增字段

2.如果插入数据时id字段指定了具体的值,就直接使用语句里指定的值

假设,某次要插入的值是X,当前的自增值是Y

1.如果X<Y,那么这个表的自增值不变

2.如果X>=Y,就需要把当前自增值修改为新的自增值

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

三、自增值的修改时机

创建一个表t,其中id是自增主键字段、c是唯一索引,建表语句如下:

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里面已经有了(1,1,1)这条记录,这时再执行一条插入数据命令:

insert into t values(null, 1, 1); 

执行流程如下:

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

2.InnoDB发现用于没有指定自增id的值,获取表t当前的自增值2

3.将传入的行的值改成(2,1,1)

4.将表的自增值改成3

5.继续执行插入数据操作,由于已经存在c=1的记录,所以报Duplicate key error(唯一键冲突),语句返回

对应的执行流程图如下:

在这里插入图片描述

在这之后,再插入新的数据行时,拿到的自增id就是3。出现了自增主键不连续的情况

唯一键冲突和事务回滚都会导致自增主键id不连续的情况

四、自增锁的优化

自增id锁并不是一个事务锁,而是每次申请完就马上释放,以便允许别的事务再申请

但在MySQL5.0版本的时候,自增锁的范围是语句级别。也就是说,如果一个语句申请了一个表自增锁,这个锁会等语句执行结束以后才释放

MySQL5.1.22版本引入了一个新策略,新增参数innodb_autoinc_lock_mode,默认值是1

1.这个参数设置为0,表示采用之前MySQL5.0版本的策略,即语句执行结束后才释放锁

2.这个参数设置为1

  • 普通insert语句,自增锁在申请之后就马上释放
  • 类似insert … select这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放

3.这个参数设置为2,所有的申请自增主键的动作都是申请后就释放锁

为了数据的一致性,默认设置为1

在这里插入图片描述

如果sessionB申请了自增值以后马上就释放自增锁,那么就可能出现这样的情况:

  • sessionB先插入了两行数据(1,1,1)、(2,2,2)
  • sessionA来申请自增id得到id=3,插入了(3,5,5)
  • 之后,sessionB继续执行,插入两条记录(4,3,3)、(5,4,4)

当binlog_format=statement的时候,两个session是同时执行插入数据命令的,所以binlog里面对表t2的更新日志只有两种情况:要么先记sessionA的,要么先记录sessionB的。无论是哪一种,这个binlog拿到从库执行,或者用来恢复临时实例,备库和临时实例里面,sessionB这个语句执行出来,生成的结果里面,id都是连续的。这时,这个库就发生了数据不一致

解决这个问题的思路:

1)让原库的批量插入数据语句,固定生成连续的id值。所以,自增锁直到语句执行结束才释放,就是为了达到这个目的

2)在binlog里面把插入数据的操作都如实记录进来,到备库执行的时候,不再依赖于自增主键去生成。也就是把innodb_autoinc_lock_mode设置为2,同时binlog_format设置为row

如果有批量插入数据(insert … select、replace … select和load data)的场景时,从并发插入数据性能的角度考虑,建议把innodb_autoinc_lock_mode设置为2,同时binlog_format设置为row,这样做既能并发性,又不会出现数据一致性的问题

对于批量插入数据的语句,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);

insert … select,实际上往表t2中插入了4行数据。但是,这四行数据是分三次申请的自增id,第一次申请到了id=1,第二次被分配了id=2和id=3,第三次被分配到id=4到id=7

由于这条语句实际上只用上了4个id,所以id=5到id=7就被浪费掉了。之后,再执行insert into t2 values(null, 5,5),实际上插入了的数据就是(8,5,5)

这是主键id出现自增id不连续的第三种原因

五、自增主键用完了

自增主键字段在达到定义类型上限后,再插入一行记录,则会报主键冲突的错误

以无符号整型(4个字节,上限就是 2 32 − 1 2^{32}-1 232−1)为例,通过下面这个语句序列验证一下:

CREATE TABLE t ( id INT UNSIGNED auto_increment PRIMARY KEY ) auto_increment = 4294967295;
INSERT INTO t VALUES(NULL);
INSERT INTO t VALUES(NULL);

第一个insert语句插入数据成功后,这个表的AUTO_INCREMENT没有改变(还是4294967295),就导致了第二个insert语句又拿到相同的自增id值,再试图执行插入语句,报主键冲突错误

推荐资料

https://time.geekbang.org/column/article/80531

到此这篇关于详解MySQL自增主键的实现的文章就介绍到这了,更多相关MySQL自增主键内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MySQL中count()和count(1)有何区别以及哪个性能最好详解

    MySQL中count()和count(1)有何区别以及哪个性能最好详解

    count是一个函数,用来统计数据,但是count函数传入的参数有很多种,比如count(1)、count(*)、count(字段)等,下面这篇文章主要给大家介绍了关于MySQL中count()和count(1)有何区别以及哪个性能最好的相关资料,需要的朋友可以参考下
    2022-08-08
  • mysql 模糊搜索的方法介绍

    mysql 模糊搜索的方法介绍

    本文将提供两种方法,第一种最土的方法:使用like语句第二种用全文索引,接下来将为你详细介绍
    2012-11-11
  • MySQL 整体架构介绍

    MySQL 整体架构介绍

    这篇文章主要介绍了MySQL 整体架构的相关资料,帮助大家更好的了解和使用MySQL数据库,感兴趣的朋友可以了解下
    2020-10-10
  • win10家庭版64位下mysql 8.0.15 安装配置方法图文教程

    win10家庭版64位下mysql 8.0.15 安装配置方法图文教程

    这篇文章主要为大家详细介绍了win10家庭版64位下mysql 8.0.15 安装配置方法图文教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-03-03
  • Mysql中分布式锁的具体实现

    Mysql中分布式锁的具体实现

    分布式锁开发中经常使用,本文主要介绍了Mysql中分布式锁的具体实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-01-01
  • Mysql日期格式以及内置日期函数用法详解

    Mysql日期格式以及内置日期函数用法详解

    MySQL中有多种数据类型可以用于日期和时间的表示,这篇文章主要给大家介绍了关于Mysql日期格式以及内置日期函数用法的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • mysql查询上下级机构的方法实例

    mysql查询上下级机构的方法实例

    大家应该都知道表里有上下级机构的,下面这篇文章主要给大家介绍了关于mysql查询上下级机构的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • 详解Mysql中的JSON系列操作函数

    详解Mysql中的JSON系列操作函数

    新版 Mysql 中加入了对 JSON Document 的支持,可以创建 JSON 类型的字段,并有一套函数支持对JSON的查询、修改等操作,下面就实际体验一下
    2016-07-07
  • MySQL中按照多字段排序及问题解决

    MySQL中按照多字段排序及问题解决

    这篇文章主要介绍了MySQL中按照多字段排序及问题解决的方法,非常的实用,有需要的小伙伴可以参考下。
    2015-03-03
  • 完美转换MySQL的字符集 解决查看utf8源文件中的乱码问题

    完美转换MySQL的字符集 解决查看utf8源文件中的乱码问题

    本人转换过好多数据了,也用过了好多的办法,个人感觉最好用的就是使用MySQL命令导出导入中将字符集转换过去
    2011-11-11

最新评论