MySQL中自增id用完怎么办?

MySQL中自增id用完怎么办?

MySQL里有很多自增的id,每个自增id都是定义了初始值,然后不停地往上加步长。虽然自然数是没有上限的,但是在计算机里,只要定义了表示这个数的字节长度,那它就有上限。比如,无 符号整型(unsigned int)是4个字节,上限就是2^32 - 1。

接下来我们看看MySQL里面的几种自增id,分析下它们的值达到上限之后,会出现什么情况。

一,表定义自增值id

表定义的自增值达到上限后的逻辑是:再次申请下一个id时,得到的值保持不变。

CREATE TABLE t (id INT UNSIGNED auto_increment PRIMARY KEY
) auto_increment = 4294967295;
SELECT * from t;
insert into t values(null);
SELECT * from t;
insert into t values(null);
SELECT * from t;

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

解决方法:

2^32 - 1(4294967295)不是一个特别大的数,对于一个频繁插入删除数据的表来说,是可能会被用完的。因此在建表的时候你需要考察你的表是否有可能达到这个上限,如果有可能,就应该创 建成8个字节的bigint unsigned。

类型         字节/bytes      范围(无符号unsigned)               范围(有符号signed)
tinyint      1              0  ~  2^8-1                       -2^7  ~  2^7-1   
smallint     2              0  ~  2^16-1                      -2^15  ~  2^15-1
mediumint    3              0  ~  2^24-1                      -2^23  ~  2^23-1
int          4              0  ~  2^32-1                      -2^31  ~  2^31-1
bigint       8              0  ~  2^64-1                      -2^63  ~  2^63-1

二,InnoDB系统自增row_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能写到数据表中的值,就有两个特征:

  1. row_id写入表中的值范围,是从0到2^48 - 1;
  2. 当dict_sys.row_id=2^48 时,如果再有插入数据的行为要来申请row_id,拿到以后再取最后6个字节的话就是0。

也就是说,写入表的row_id是从0开始到2^48 -1。达到上限后,下一个值就是0,然后继续循环。

验证:

在这里插入图片描述

从这个角度看,我们还是应该在InnoDB表中主动创建自增主键。因为,表自增id到达上限后, 再插入数据时报主键冲突错误,是更能被接受的。

毕竟覆盖数据,就意味着数据丢失,影响的是数据可靠性;报主键冲突,是插入失败,影响的是 可用性。而一般情况下,可靠性优先于可用性。

三,Xid

XidMySQL内部是怎么生成的呢?

redo logbinlog相配合的时候,它们有一个共同的字段叫作Xid。它在MySQL中是用来对应事务的。

MySQL内部维护了一个全局变量global_query_id,每次执行语句的时候将它赋值给Query_id, 然后给这个变量加1。如果当前语句是这个事务执行的第一条语句,那么MySQL还会同时把 Query_id赋值给这个事务的Xid

global_query_id是一个纯内存变量,重启之后就清零了。所以你就知道了,在同一个数据库实例中,不同事务的Xid也是有可能相同的。

但是MySQL重启之后会重新生成新的binlog文件,这就保证了,同一个binlog文件里,Xid一定是惟一的。

虽然MySQL重启不会导致同一个binlog里面出现两个相同的Xid,但是如果global_query_id达到上限后,就会继续从0开始计数。从理论上讲,还是就会出现同一个binlog里面出现相同Xid的场景。

因为global_query_id定义的长度是8个字节,这个自增值的上限是2^64 - 1。要出现这种情况,必须是下面这样的过程:

  1. 执行一个事务,假设Xid是A;
  2. 接下来执行2^64 次查询语句,让global_query_id回到A;
  3. 再启动一个事务,这个事务的Xid也是A。

不过,2^64 这个值太大了,大到你可以认为这个可能性只会存在于理论上。

四,Innodb trx_id

Xid和InnoDB的trx_id是两个容易混淆的概念。

Xid是由server层维护的。InnoDB内部使用Xid,就是为了能够在InnoDB事务和server之间做关联。但是,InnoDB自己的trx_id,是另外维护的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其实,trx_id就是mvcc中用到的事务id(transaction id)。

InnoDB数据可见性的核心思想是:每一行数据都记录了更新它的trx_id,当一个事务读到一行数据的时候,判断这个数据是否可见的方法,就是通过事务的一致性视图与这行数据的trx_id做对 比。

InnoDB内部维护了一个max_trx_id全局变量,每次需要申请一个新的trx_id时,就获得 max_trx_id的当前值,然后并将max_trx_id加1。

对于正在执行的事务(活跃事务),你可以从information_schema.innodb_trx表中看到事务的一些相关信息如:trx_id

现在,我 们一起来看一个事务现场:

在这里插入图片描述

事务的trx_id session B里,我从innodb_trx表里查出的这两个字段,第二个字段trx_mysql_thread_id就是线程 id。显示线程id,是为了说明这两次查询看到的事务对应的线程id都是5,也就是session A所在的线程。

可以看到,T2时刻显示的trx_id是一个很大的数;T4时刻显示的trx_id是1289,看上去是一个比 较正常的数字。这是什么原因呢?

实际上,在T1时刻,session A还没有涉及到更新,是一个只读事务。而对于只读事务,InnoDB 并不会分配trx_id。也就是说:

  1. 在T1时刻,trx_id的值其实就是0。而这个很大的数,只是显示用的。一会儿我会再和你说说 这个数据的生成逻辑。
  2. 直到session A 在T3时刻执行insert语句的时候,InnoDB才真正分配了trx_id。所以,T4时刻,session B查到的这个trx_id的值就是1289。

需要注意的是,除了显而易见的修改类语句外,如果在select 语句后面加上for update,这个事 务也不是只读事务。

实验的时候发现不止加1。这是因为

  1. update 和 delete语句除了事务本身,还涉及到标记删除旧数据,也就是要把数据放到purge 队列里等待后续物理删除,这个操作也会把max_trx_id + 1, 因此在一个事务中至少加2;
  2. InnoDB的后台操作,比如表的索引信息统计这类操作,也是会启动内部事务的,因此你可能看到,trx_id值并不是按照加1递增的。

那么,T2时刻查到的这个很大的数字是怎么来的呢

其实,这个数字是每次查询的时候由系统临时计算出来的。它的算法是:把当前事务的trx变量的 指针地址转成整数,再加上2^48。使用这个算法,就可以保证以下两点:

  1. 因为同一个只读事务在执行期间,它的指针地址是不会变的,所以不论是在 innodb_trx还是 在innodb_locks表里,同一个只读事务查出来的trx_id就会是一样的。
  2. 如果有并行的多个只读事务,每个事务的trx变量的指针地址肯定不同。这样,不同的并发只读事务,查出来的trx_id就是不同的。

为什么还要再加上2^48 呢?

在显示值里面加上2^48 ,目的是要保证只读事务显示的trx_id值比较大,正常情况下就会区别于读 写事务的id。但是,trx_id跟row_id的逻辑类似,定义长度也是8个字节。因此,在理论上还是可 能出现一个读写事务与一个只读事务显示的trx_id相同的情况。不过这个概率很低,并且也没有 什么实质危害,可以不管它。

只读事务不分配trx_id,有什么好处呢?

  • 一个好处是,这样做可以减小事务视图里面活跃事务数组的大小。因为当前正在运行的只读 事务,是不影响数据的可见性判断的。所以,在创建事务的一致性视图时,InnoDB就只需要 拷贝读写事务的trx_id。
  • 另一个好处是,可以减少trx_id的申请次数。在InnoDB里,即使你只是执行一个普通的select 语句,在执行过程中,也是要对应一个只读事务的。所以只读事务优化后,普通的查询语句 不需要申请trx_id,就大大减少了并发事务申请trx_id的锁冲突。

由于只读事务不分配trx_id,一个自然而然的结果就是trx_id的增加速度变慢了。

但是,max_trx_id会持久化存储,重启也不会重置为0,那么从理论上讲,只要一个MySQL服务 跑得足够久,就可能出现max_trx_id达到2^48 -1的上限,然后从0开始的情况。

当达到这个状态后,MySQL就会持续出现一个脏读的bug,我们来复现一下这个bug。

首先我们需要把当前的max_trx_id先修改成2^48 -1。注意:这个case里使用的是可重复读隔离级 别。具体的操作流程如下:

create table t(id int primary key, c int)engine=innodb;
insert into t values(1,1);
gdb -p <pid.mysqld> -ex 'p trx_sys->max_trx_id=281474976710655' --batch;

在这里插入图片描述

由于我们已经把系统的max_trx_id设置成了2^48 -1,所以在session A启动的事务TA的低水位就是 2^48 - 1。

在T2时刻,session B执行第一条update语句的事务id就是2^48 - 1,而第二条update语句的事务id 就是0了,这条update语句执行后生成的数据版本上的trx_id就是0。

在T3时刻,session A执行select语句的时候,判断可见性发现,c=3这个数据版本的trx_id,小于事务TA的低水位,因此认为这个数据可见。

但,这个是脏读。

由于低水位值会持续增加,而事务id从0开始计数,就导致了系统在这个时刻之后,所有的查询都会出现脏读的。

并且,MySQL重启时max_trx_id也不会清0,也就是说重启MySQL,这个bug仍然存在。

那么,这个bug也是只存在于理论上吗

假设一个MySQL实例的TPS是每秒50万,持续这个压力的话,在17.8年后,就会出现这个情 况。如果TPS更高,这个年限自然也就更短了。但是,从MySQL的真正开始流行到现在,恐怕 都还没有实例跑到过这个上限。不过,这个bug是只要MySQL实例服务时间够长,就会必然出现的。

五,thread_id

接下来,我们再看看线程id(thread_id)。其实,线程id才是MySQL中最常见的一种自增id。平时我们在查各种线程的时候,show processlist里面的第一列,就是thread_id。

thread_id的逻辑很好理解:系统保存了一个全局变量thread_id_counter,每新建一个连接,就 将thread_id_counter赋值给这个新连接的线程变量。

thread_id_counter定义的大小是4个字节,因此达到2^32 -1后,它就会重置为0,然后继续增加。 但是,你不会在show processlist里看到两个相同的thread_id。

这,是因为MySQL设计了一个唯一数组的逻辑,给新线程分配thread_id的时候,逻辑代码是这 样的:

do {
new_id= thread_id_counter++;
} while (!thread_ids.insert_unique(new_id).second);

总结

每种自增id有各自的应用场景,在达到上限后的表现也不同:

  1. 表的自增id达到上限后,再申请时它的值就不会改变,进而导致继续插入数据时报主键冲突 的错误。
  2. row_id达到上限后,则会归0再重新递增,如果出现相同的row_id,后写的数据会覆盖之前 的数据。
  3. Xid只需要不在同一个binlog文件中出现重复值即可。虽然理论上会出现重复值,但是概率极 小,可以忽略不计。
  4. InnoDB的max_trx_id 递增值每次MySQL重启都会被保存起来,所以我们文章中提到的脏读 的例子就是一个必现的bug,好在留给我们的时间还很充裕。
  5. thread_id是我们使用中最常见的,而且也是处理得最好的一个自增id逻辑了。

不同的自增id有不同的上限值,上限值的大小取决于声明的类型长度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/162695.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python数据结构与算法-15_堆与堆排序

堆(heap) 前面我们讲了两种使用分治和递归解决排序问题的归并排序和快速排序&#xff0c;中间又穿插了一把树和二叉树&#xff0c; 本章我们开始介绍另一种有用的数据结构堆(heap)&#xff0c; 以及借助堆来实现的堆排序&#xff0c;相比前两种排序算法要稍难实现一些。 最后我…

Linux开发工具(含gdb调试教程)

文章目录 Linux开发工具&#xff08;含gdb调试教程&#xff09;1、Linux 软件包管理器 yum2、Linux开发工具2.1、Linux编辑器 -- vim的使用2.1.1、vim的基本概念2.1.2、vim的基本操作2.1.3、vim正常模式命令集2.1.4、vim末行模式命令集 2.2、vim简单配置 3、Linux编译器 -- gcc…

redis之cluster集群

1、redis-cluster集群&#xff1a;redis3.0引入的分布式存储方案 2、集群&#xff1a;由多个node节点组成&#xff0c;redis数据分布在这些节点之中 &#xff08;1&#xff09;在集群之中也分主节点和从节点 &#xff08;2&#xff09;自带哨兵模式 3、redis-cluster集群的…

腾讯云 小程序 SDK对象存储 COS使用记录,原生小程序写法。

最近做了一个项目&#xff0c;需求是上传文档&#xff0c;文档类型多种&#xff0c;图片&#xff0c;视频&#xff0c;文件&#xff0c;doc,xls,zip,txt 等等,而且文档类型可能是大文件&#xff0c;可能得上百兆&#xff0c;甚至超过1G。 腾讯云文档地址&#xff1a;https://c…

PC端页面进去先出现加载效果

自定义指令v-loading&#xff0c;只需要绑定Boolean即可 v-loading“loading” <el-table :data"list" border style"width: 100%" v-loading"loading"><el-table-column align"center" label"序号" width"5…

开发板启动进入系统以后再挂载 NFS 文件系统, 这里的NFS文件系统是根据正点原子教程制作的ubuntu_rootfs

如果是想开发板启动进入系统以后再挂载 NFS 文件系统&#xff0c;开发板启动进入文件系统&#xff0c;开发板和 ubuntu 能互相 ping 通&#xff0c;在开发板文件系统下新建一个目录 you&#xff0c;然后执行如下指令进行挂载&#xff1a; mkdir mi mount -t nfs -o nolock,nfsv…

日本it就职培训机构,日本IT行业的三种类型

日本的IT产业一直保持增长趋势&#xff0c;市场规模逐年增加&#xff0c;在日本所有产业中占据很大比例。由于日本老龄化严重&#xff0c;日本国内的IT人才无法满足需求&#xff0c;为缓解这一问题&#xff0c;日本将引进外国优秀IT人才作为一项国策&#xff0c;日本IT行业不仅…

Leetcode1410. HTML 实体解析器

Every day a Leetcode 题目来源&#xff1a;1410. HTML 实体解析器 解法1&#xff1a;模拟 遍历字符串 text&#xff0c;每次遇到 ’&‘&#xff0c;就判断以下情况&#xff1a; 双引号&#xff1a;字符实体为 &quot; &#xff0c;对应的字符是 " 。单引号&a…

振弦式土压力计在岩土工程安全监测应用的方案

振弦式土压力计在岩土工程安全监测应用的方案 振弦式土压力计是一种常见的土压力测量仪器&#xff0c;其原理是利用振弦在土中传播的速度与土的应力状态有关的特点测量土压力。在岩土工程安全监测中&#xff0c;振弦式土压力计可以应用于以下方面&#xff1a; 1. 地下连续墙和…

某资产管理机构: IAST提升安全水平,保障资产管理水平稳健增长

某资产管理机构是国内首批成立的资产管理公司之一&#xff0c;坚持“科技金融”、“数字金融”战略&#xff0c;以客户为中心&#xff0c;聚焦用户体验与业务协同&#xff0c;着力推进营销数字化进程和大数据平台建设&#xff0c;助力资产管理高质量发展。 数字科技推动工作效率…

面试题:Java 对象不使用时,为什么要赋值 null ?

文章目录 前言示例代码运行时栈典型的运行时栈Java的栈优化提醒 GC一瞥提醒 JVM的“BUG”总结 前言 最近&#xff0c;许多Java开发者都在讨论说&#xff0c;“不使用的对象应手动赋值为null“ 这句话&#xff0c;而且好多开发者一直信奉着这句话&#xff1b;问其原因&#xff…

【Flask使用】全知识md文档,4大部分60页第3篇:Flask模板使用和案例

本文的主要内容&#xff1a;flask视图&路由、虚拟环境安装、路由各种定义、状态保持、cookie、session、模板基本使用、过滤器&自定义过滤器、模板代码复用&#xff1a;宏、继承/包含、模板中特有变量和函数、Flask-WTF 表单、CSRF、数据库操作、ORM、Flask-SQLAlchemy…

nvm切换版本之后npm用不了

原因是 nvm只给你安了对应的node没给你安装对应的node版本的npm 解决办法如下 1找到你安装的node版本号 然后去官网下载对应的版本包 这个网址就是node官网的版本列表 Index of /download/release/ 2下载后解压 把根目录这俩复制到自己的nvm安装目录下 还有那个node_modul…

Java【XML 配置文件解析】

前言 最近考试周忙得要死&#xff0c;但我却不紧不慢&#xff0c;还有三天复习时间&#xff0c;考试科目几乎都还没学呢。今天更新一个算是工具类-XML文件的解析&#xff0c;感觉还是挺有用的&#xff0c;之后可以融进自己的项目里。 XML 配置文件解析 0、导入依赖 有点像我…

企业软件定制开发的优势|app小程序网站搭建

企业软件定制开发的优势|app小程序网站搭建 企业软件定制开发是一种根据企业特定需求开发定制化软件的服务。相比于购买现成的软件产品&#xff0c;企业软件定制开发具有许多优势。 1.企业软件定制开发可以满足企业独特需求。每个企业都有自己独特的业务流程和需求&#xff0c;…

在 Redis 中使用 JSON 文档:命令行界面(CLI)和 Navicat 集成

Redis&#xff0c;因其极高的性能而闻名&#xff0c;是一款多功能的 NoSQL 数据库&#xff0c;擅长处理键值对。虽然 Redis主要用于处理简单数据结构&#xff0c;但是同样支持更多复杂的数据类型&#xff0c;如列表、集合甚至是 JSON 文件。在本文&#xff0c;我们将深入到 Red…

SAP LU04记账更改通知单创建转储单报错:L3094 记帐修改没有份存在

解决办法&#xff1a; 使用事务码LU02&#xff0c;修改过账更改状态&#xff0c;将过账更改状态改为U&#xff0c;强制关闭 1. LU04 查找记账更改通知单号 2. 事务码LU02修改状态 这个时候再用LU04去查看的时候&#xff0c;就不会再显示了

技术短视频账号矩阵seo系统--源头开发---saas工具

专注短视频账号矩阵系统源头开发---saas营销化工具&#xff0c;目前我们作为一家纯技术开发团队目前已经专注打磨开发这套系统企业版/线下版两个版本的saas营销拓客工具已经3年了&#xff0c;本套系统逻辑主要是从ai智能批量剪辑、账号矩阵全托管发布、私信触单收录、文案ai智能…

网络安全等级保护2.0国家标准

等级保护2.0标准体系主要标准如下&#xff1a;1.网络安全等级保护条例2.计算机信息系统安全保护等级划分准则3.网络安全等级保护实施指南4.网络安全等级保护定级指南5.网络安全等级保护基本要求6.网络安全等级保护设计技术要求7.网络安全等级保护测评要求8.网络安全等级保护测评…

【AGC】云存储服务端使用方法

【集成准备】 1、Python环境配置 下载Python和PyCharm并安装。 ​ 使用安装的python本身作为解释器。 ​ 安装AGC Python SDK。 ​云存储包安装完成。 ​ 2、AGC环境配置 在AGC创建项目和应用 ​ 开通云存储服务。 返回项目设置界面&#xff0c;选择Server SDK 页签…