我们知道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写到数据库的值有两个特征
也就是说写入表的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,使用这个算法,可以保证下面两点
我们这个算法为什么要加2^48
目的就是保证只读事务显示trx_id值比较大,这个只是为了在正常情况下,区别读写事务的id,但是trx_id跟row_id的逻辑类似,定义为8个字节,因此理论上可能会出现一个读写事务和只读事务显示的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);
如果对您有一丝丝帮助,麻烦点个关注,也欢迎转发,谢谢
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有