InnoDB 隐藏列:DB_TRX_ID 与 DB_ROLL_PTR

InnoDB 隐藏列:DB_TRX_ID 与 DB_ROLL_PTR

概述

InnoDB 的聚簇索引(Clustered Index)中,每一行记录除了用户定义的字段外,还包含一些系统隐藏列。其中最重要的两个隐藏列是 DB_TRX_IDDB_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'(新事务看到新版本)

面试要点

  1. 两个核心隐藏列:DB_TRX_ID(谁改的)+ DB_ROLL_PTR(原来的版本在哪)
  2. 版本链:由 DB_ROLL_PTR 串联起来的 Undo Log 链条
  3. 可见性判断:DB_TRX_ID 是 Read View 判断可见性的依据
  4. 面试高频:面试官问 “MVCC 如何实现?” → 必须讲隐藏列
  5. Purge 清旧版本:当版本链不再被任何 Read View 需要时,Purge 线程会清理
© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容