InnoDB的ID家族[ROW_ID,XID,TRX-ID,THREAD-ID]

前言 随着对mysql的深入理解,以及接触mysql越来的频繁,发现mysql的这些ID们在整个mysql中的作用十分可观,因此对于这些ID,专门写一篇文章来让大家介绍和深入的理解,目前文章中列出来的是比较常用的和我所想到的这几个ID,后续有其他的会继续补充。
一、什么是ROW_ID? ROW_ID

If a table has a PRIMARY KEY or UNIQUE NOT NULL index that consists of a single column that has an integer type, you can use _rowid to refer to the indexed column in SELECT statements
简单翻译一下,如果在表中存在主键或非空唯一索引,并且仅由一个整数类型的列构成,那么就可以使用SELECT语句直接查询_rowid,也就是说row_id就是行索引id。
总结一下就是三个条件:
1. ,主键
2. ,非空唯一索引
3. ,整数类型
接下来看看这些条件在以下各个场景组合会发生什么?
结果就先放出来:
InnoDB的ID家族[ROW_ID,XID,TRX-ID,THREAD-ID]
文章图片

  • 若只存在主键+整型 -》 row_id生效 ,取主键值
  • 若只存在非空唯一索引+整型 -》row_id生效 ,取非空索引值
  • 若存在多个非空唯一索引 -》 row_id严格取第一个,满足就有效,第一个不满足就失效。
    • 若主键和非空唯一索引同时存在 -》row_id生效,取主键值。
    • 若主键和非空唯一索引都不存在:取全局变量dictsys.row_id
    测试场景1:
    设置了主键,并且主键字段是数值类型的情况下,_rowid直接引用了主键字段的值,这种叫显示rowid。
InnoDB的ID家族[ROW_ID,XID,TRX-ID,THREAD-ID]
文章图片

改了主键类型为varchar,就报错了。
InnoDB的ID家族[ROW_ID,XID,TRX-ID,THREAD-ID]
文章图片

其他的几个场景我就不在这一一举例了,有兴趣的可以自己去测试一下。
二、什么是XID? XID
Xid 是由 server 层维护的。InnoDB 内部使用 Xid,就是为了能够在 InnoDB 事务和 server 之间做关联。
Xid 在 MySQL 内部的生成逻辑:
  1. MySQL内部维护一个全局变量global_query_id**
  2. 每次执行语句的时候将global_query_id -> Query_id, 然后global_query_id+1**
  3. if(事务执行的第一条语句){Query_id->XID}**
global_query_id是一个纯内存变量,重启之后清零
? 因此,在同一个数据库实例中,不同事务的XID也有可能是相同的
? MySQL重启之后,会重新生成新的binlog
保证:同一个binlog文件里,XID是唯一的
? global_query_id达到上限后,就会继续从0开始计数
因此理论上,同一个binlog还是会出现相同的XID,只是概率极低
【InnoDB的ID家族[ROW_ID,XID,TRX-ID,THREAD-ID]】global_query_id是8 Bytes,上限为2^64-1
? 执行一个事务,假设XID是A
? 接下来执行2^64次查询语句,让global_query_id回到A
? 再启动一个事务,这个事务的XID也是A(类似row_id超出最大值后清零,从新开始)
xid的存在的意义
redo log 和 binlog 有一个共同的数据字段,叫 XID,崩溃恢复的时候,会按顺序扫描 redo log:
  • 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;
  • 如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务。
三、什么是TRX_ID? TRX_ID
Xid 和 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。
现在,我们一起来看一个事务现场:
InnoDB的ID家族[ROW_ID,XID,TRX-ID,THREAD-ID]
文章图片

1. 事务1 时刻
session A 还没有涉及到更新,是一个只读事务。
对于只读事务,InnoDB 并不会分配 trx_id
所以事务2时刻去查事务trx_id是一个很大的值,这个很大的trx_id是由系统临时计算出来的,是把当前事务的trx变量的指针地址转成整数,再加上2^48。
为什么值这么大?
目的是要保证只读事务显示的 trx_id 值比较大,正常情况下就会区别于读写事务的 id
只读事务不分配trx_id的好处?
? 可以减少事务视图里面活跃数组的大小
当前正在运行的只读事务,是不影响数据的可见性判断 因此,在创建事务的一致性视图时,只需要拷贝读写事务的trx_id

? 可以减少trx_id的申请次数
在InnoDB里,即使只执行一条普通的SELECT语句,在执行过程中,也要对应一个只读事务 如果普通查询语句不申请trx_id,就可以大大减少并发事务申请trx_id的锁冲突 由于只读事务不分配trx_id,trx_id的增加速度会变慢。

1. 事务3 时刻
此时,InnoDB才真正分配trx_id。
四、什么是THREAD_ID? THREAD_ID
1、SHOW PROCESSLIST的第一列就是thread_id
2、系统保存了一个环境变量thread_id_counter
? 每新建一个连接,就将thread_id_counter赋值给这个新连接的线程变量
3、thread_id_counter定义为4 Bytes,因此达到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);

以上,就是mysql中比较常用的这几个ID,后续有其他的会继续补充,希望大家多多关注支持。

    推荐阅读