InnoDB 隐藏列:DB_TRX_ID 与 DB_ROLL_PTR
概述
InnoDB 的聚簇索引(Clustered Index)中,每一行记录除了用户定义的字段外,还包含一些系统隐藏列。其中最重要的两个隐藏列是 DB_TRX_ID 和 DB_ROLL_PTR,它们是 MVCC 多版本并发控制的基石。
隐藏列总览
| 隐藏列 | 名称 | 作用 | 大小 |
|---|---|---|---|
| DB_ROW_ID | 行 ID | 无主键时自动生成 | 6 字节 |
| DB_TRX_ID | 事务 ID | 最近修改本行的事务 ID | 6 字节 |
| DB_ROLL_PTR | 回滚指针 | 指向 Undo Log 中旧版本记录 | 7 字节 |
注意:DB_ROW_ID 只在表没有定义主键时才使用。InnoDB 强烈建议为每张表设置显式主键。
DB_TRX_ID:事务标识列
定义
DB_TRX_ID 记录了最后一次修改这行数据的事务 ID。
工作方式
-- 初始状态:id=1 的行的 DB_TRX_ID=10(被事务 10 插入)
-- 事务 20 修改这行
BEGIN; -- 事务 ID = 20
UPDATE user SET name = '张三' WHERE id = 1;
COMMIT;
-- 修改后,该行的 DB_TRX_ID = 20
-- 事务 30 再次修改
BEGIN; -- 事务 ID = 30
UPDATE user SET name = '李四' WHERE id = 1;
COMMIT;
-- 修改后,该行的 DB_TRX_ID = 30
在 MVCC 中的作用
当一条 SELECT 语句读取某行时:
DB_TRX_ID 的值
│
▼
与当前事务的 Read View 进行比较
│
├── DB_TRX_ID 对应的版本对当前事务可见
│ └── ✅ 直接返回该行数据
│
└── DB_TRX_ID 对应的版本对当前事务不可见
└── ❌ 通过 DB_ROLL_PTR 找到旧版本
可见性判断示例
-- Read View 当前活跃事务列表: [20, 22]
-- 当前事务 ID: 25
-- 读取 id=1 的行
-- 该行 DB_TRX_ID = 20(由事务 20 修改)
-- 事务 20 在活跃事务列表中 → 该版本不可见
-- 读取 id=2 的行
-- 该行 DB_TRX_ID = 18(由事务 18 修改)
-- 事务 18 < Read View 中最小的活跃事务 → 该版本可见(已提交)
DB_ROLL_PTR:回滚指针列
定义
DB_ROLL_PTR 指向 Undo Log 中的前一个版本的记录。通过它,InnoDB 可以构建出一条数据的完整版本链。
版本链结构
当前版本(最新)
DB_TRX_ID = 30
DB_ROLL_PTR ──────┐
│
▼
Undo Segment
DB_TRX_ID = 20(上一个版本)
DB_ROLL_PTR ──────┐
│
▼
Undo Segment
DB_TRX_ID = 10(最初版本)
DB_ROLL_PTR = NULL(版本链终点)
API 级别的查看(5.6+)
理论上可以通过虚拟列访问,但在 MySQL 中直接查看隐藏列需要通过特定方式:
-- InnoDB 没有公开 SQL 接口直接查看隐藏列
-- 但可以通过 information_schema 间接了解事务信息
-- 查看最近操作事务
SELECT * FROM information_schema.innodb_trx\G
-- MySQL 8.0+ 的 performance_schema 可以追踪行变更
隐藏列的生成时机
插入操作
INSERT INTO user(name) VALUES('张三');
-- 新行:
-- DB_TRX_ID = 当前插入事务 ID
-- DB_ROLL_PTR = NULL(没有旧版本)
更新操作
UPDATE user SET name = '李四' WHERE id = 1;
-- 修改前:
-- 旧行:DB_TRX_ID = T1, DB_ROLL_PTR = ptr_old
-- 修改后:
-- 旧行:复制旧值到 Undo Log,DB_ROLL_PTR 指向新旧链接
-- 新行:DB_TRX_ID = T2(当前事务), DB_ROLL_PTR → Undo 中的旧行
删除操作
删除在 InnoDB 中并不是物理删除,而是标记删除:
DELETE FROM user WHERE id = 1;
-- 行被标记为删除
-- DB_TRX_ID 记录删除操作的事务 ID
-- 真正的物理删除由 Purge 线程在版本不再被任何人需要时执行
实际操作示例
创建测试表
CREATE TABLE test_mvcc (
id INT PRIMARY KEY,
name VARCHAR(20)
) ENGINE=InnoDB;
INSERT INTO test_mvcc VALUES(1, 'Alice'), (2, 'Bob');
观察隐藏列的效果
虽然不能直接 SELECT 隐藏列,但可以通过事务行为来观察:
-- 会话 A
BEGIN;
SELECT * FROM test_mvcc WHERE id = 1; -- → 'Alice'
-- 会话 B
UPDATE test_mvcc SET name = 'Charlie' WHERE id = 1;
COMMIT;
-- 会话 A(REPEATABLE READ)
SELECT * FROM test_mvcc WHERE id = 1; -- → 'Alice'(还是旧值!)
-- 因为 DB_TRX_ID 对应的版本对事务 A 不可见
-- 通过 DB_ROLL_PTR 回溯到了旧版本
-- 会话 A COMMIT 后
COMMIT;
SELECT * FROM test_mvcc WHERE id = 1; -- → 'Charlie'(新事务看到新版本)
面试要点
- 两个核心隐藏列:DB_TRX_ID(谁改的)+ DB_ROLL_PTR(原来的版本在哪)
- 版本链:由 DB_ROLL_PTR 串联起来的 Undo Log 链条
- 可见性判断:DB_TRX_ID 是 Read View 判断可见性的依据
- 面试高频:面试官问 “MVCC 如何实现?” → 必须讲隐藏列
- Purge 清旧版本:当版本链不再被任何 Read View 需要时,Purge 线程会清理
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容