前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自增id用完怎么办

自增id用完怎么办

作者头像
小土豆Yuki
发布2021-03-03 15:32:21
1K0
发布2021-03-03 15:32:21
举报

我们知道mysql中存在很多自增id,然后不断增长,由于只要给id定义了这个数的字节长度,那么他就有了上限,比如无符号整型(unsigned int)是4个字节,因此他的上限是2^32-1,

表定义自增值id

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

因此当我们达到上限的时候(id=4294967295),就会导致下一个insert语句拿到相同的id,试图执行的时候,就会报主键冲突错误,因此在建表的时候你需要你的表是否可能达到这个上限,如果有可能就应该创建8个字节的bigint unsigned

InnoDB系统自增row_id

如果你创建的InnoDB表没有指定主键,那么InnoDB会给你创建一个不可见的,长度为6字节的row_id,InnoD维护了一个dict_sys.row_id,所有无主键的InnoDB表,每插入一行数据,就会把这个值赋值给row_id,然后把dict_sys_row_id的值加1.

此时的row_id写到数据库的值有两个特征

  1. row_id写入表的范围是0-2^48-1
  2. 当dict_sys.row_id=2^48时候,在插入数据申请的row_id,就会变为0

也就是说写入表的row_id是从0开始到2^48-1,达到上限后,下一个值就是0,然后继续循环,虽然2^48-1本身就很大,但是他还是有上限的,且在innoDB逻辑里,申请row_id=N后,就将这行写入表中,如果表中已经存在row_id=N,就覆盖以前的行

由于有可能覆盖数据,意味着数据丢失,影响的是数据可靠性。而报主键冲突影响的是可用性,一般情况下,可靠性高于可用行

XID

xid是记录对应事务用的,xid是如何生成的呢?

其实在mysql内部维护了一个全局变量global_query_id,每次执行语句的时候将他赋值给Query_id,然后给这个变量加1,如果当前语句是这个事务的第一条语句,就会把Query_id赋值给xid,

而global_query_id在数据库重启之后,就会清零,所以同一个数据库实例中,不同事务的xid也是有可能相同,但是mysql重启之后也会重新生成binlog,因此binlog日志里面的xid是唯一的,但是如果global_query_id达到上限之后,就会继续从0开始计算,理论上还是会出现同一个binlog有相同的xid,又因为global_querey_id定义为8个字节,自增的上限是2^64-1,所以出现的概率很小,仅存在理论上

Innodb trx_id

首先要知道的是xid是有server层维护的,InnoDB内部使用Xid,就是为了能够在InnoDB事务和server之间做关联,但是innoDB自己trx_id,是另外维护的

innoDB内部维护了一个max_trx_id全局变量,每次申请一个姓的trx_id,就会获取max_trx_id的当前值,然后并将max_trx_id加1.

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

我们可以从information_schema.innodb_trx表中看到事物trx_id,如下图

我们发现T2时刻的trx_id的值很大,而T4时刻是一个正常的值,这个是为什么呢

在T1时刻sessionA没有涉及更新操作,是一个只读事物,而只读事务InnoDB是不会分配trx_id,但是我们还会发现一个问题,就是在执行uodate和delete执行之后,事物的trx_id不止加1,那是因为update和delete除了事物本身,还涉及到标记深处旧数据,也就是把数据导到purge队列里等到后续物理删除,这个操作也会max_trx_id+1,因此一个事物中至少加2,还有就是innoDB的后台操作,比如表达索引信息统计这个类也是会启动内部事务的因此可以看到,trx_id值并不是按照加1递增的

现在我们看看为什么T2的trx_id的为什么那么大,他的算法是把当前事务trx变量的指针地址转成整数,再加上2^48,使用这个算法,可以保证下面两点

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

我们这个算法为什么要加2^48

目的就是保证只读事务显示trx_id值比较大,这个只是为了在正常情况下,区别读写事务的id,但是trx_id跟row_id的逻辑类似,定义为8个字节,因此理论上可能会出现一个读写事务和只读事务显示的trx_id相同的情况,不过概率很低

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

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

最后我们要知道max_trx_id会持久化存储,重启也不会重置为0,理论上,只要mysql运行的时间够长,就会可能达到上限,然后从0开始的情况

因此他会出现一个脏读的bug,如下图

在T2时刻sessionB执行第一条update语句的事物id就是2^48-1,而第二条语句update,语句的事务id就是0了,

在T3时刻sessionA执行select语句的时候,判断可见性发现C=3这个数据版本的trx_id,小于事物TA的低水位,因此这个数据就是可见的,这个就是脏读的bug,由于低水位会只需增加,而事务id从0开始计数,不管是否重启数据库,这个bug任然存在。

thread_id

线程id是我们最常见的一种自增id,我使用show processlist的第一列计数就是thread_id,

thread_id的逻辑很好理解,系统保存一个全局变量thread_id_conuter,每新建一个连接,就将thread_id_counter赋值个这个新连接的线程变量,他的上限是2^32-1.当达到上限之后,就会重置为0,然后持续增加,但是并不会出现show processlist看到相同的thread_id,是因为mysql设计了一个唯一数组的逻辑,给新线程分配thread_id的收逻辑代码如下,因此不会出现相同的thread_id

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

如果对您有一丝丝帮助,麻烦点个关注,也欢迎转发,谢谢

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

本文分享自 洁癖是一只狗 微信公众号,前往查看

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

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

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