Read View 决定可见性
概述
Read View(读视图)是 InnoDB MVCC 机制中的核心数据结构,它决定了在当前事务中,哪些数据版本是”可见的”。每次事务读取数据时,InnoDB 都会通过 Read View 来判断:这个版本能看,还是不能看。
Read View 是什么
Read View 是事务在某个时间点对数据库的”快照”,它记录了该时刻系统中所有活跃事务的信息。
Read View 数据结构
│
├── m_creator_trx_id: 创建该 Read View 的事务 ID
├── m_low_limit_id: 下一个将被分配的事务 ID(上限)
├── m_up_limit_id: 当前活跃事务中最小的 ID(下限)
└── m_ids: 创建时刻所有活跃事务的 ID 列表(有序数组)
字段详解
| 字段 | 含义 | 举例 |
|---|---|---|
m_creator_trx_id |
当前事务的 ID | 25 |
m_low_limit_id |
下一个将被分配的 ID | 30(说明有 29 个子事务) |
m_up_limit_id |
活跃事务中最小的 ID | 26 |
m_ids |
快照时刻所有活跃事务 ID 列表 | [26, 28, 29] |
可见性判断规则
当 InnoDB 需要判断某行记录的当前版本(其 DB_TRX_ID = T)是否可见时,遵循以下规则:
规则一:当前事务自己的修改,总是可见
IF DB_TRX_ID == m_creator_trx_id
→ ✅ 可见(自己改的,不看自己看谁)
规则二:比上界之后的事务,不可见
IF DB_TRX_ID >= m_low_limit_id
→ ❌ 不可见(这个事务在快照之后才启动,它的修改看不到)
规则三:比下界之前的事务,总是可见
IF DB_TRX_ID < m_up_limit_id
→ ✅ 可见(这个事务在快照时已经提交了,应该看到)
规则四:在上下界之间,检查活跃事务列表
IF m_up_limit_id <= DB_TRX_ID < m_low_limit_id
→ 检查 m_ids 列表中是否包含 DB_TRX_ID
├── 包含 → ❌ 不可见(该事务在快照时刻还活跃着)
└── 不包含 → ✅ 可见(该事务在快照前已提交)
流程图
DB_TRX_ID
│
▼
┌─────────────────────┐
│ == m_creator_trx_id │ → ✅ 可见
└─────────────────────┘
│
▼
┌─────────────────────┐
│ < m_up_limit_id │ → ✅ 可见
└─────────────────────┘
│
▼
┌─────────────────────┐
│ >= m_low_limit_id │ → ❌ 不可见
└─────────────────────┘
│
(在中间范围)
│
▼
┌─────────────────────┐
│ 在 m_ids 列表中? │
├── 是 → ❌ 不可见 │
└── 否 → ✅ 可见 │
完整示例
场景设定
事务 ID 假设:
事务 T1: ID=10(已提交)
事务 T2: ID=20(已提交)
事务 T3: ID=25(当前事务)
事务 T4: ID=30(活跃)
事务 T5: ID=35(活跃)
当前事务 T3 的 Read View
m_creator_trx_id = 25
m_low_limit_id = 36(下一个即将分配的事务 ID)
m_up_limit_id = 30(活跃事务中最小的)
m_ids = [30, 35]
可见性判断
-- 读取 id=1 的行,该行 DB_TRX_ID = 10
-- 10 < m_up_limit_id(30) → ✅ 可见(事务 10 已经提交了)
-- 读取 id=2 的行,该行 DB_TRX_ID = 20
-- 20 < m_up_limit_id(30) → ✅ 可见(事务 20 也提交了)
-- 读取 id=3 的行,该行 DB_TRX_ID = 25
-- 25 == m_creator_trx_id → ✅ 可见(自己改的)
-- 读取 id=4 的行,该行 DB_TRX_ID = 30
-- 30 在 m_ids 列表中 → ❌ 不可见,需要回溯版本链
-- 读取 id=5 的行,该行 DB_TRX_ID = 40
-- 40 >= m_low_limit_id(36) → ❌ 不可见,需要回溯版本链
Read View 的创建时机
REPEATABLE READ(MySQL 默认)
-- 事务中第一次 SELECT 时创建 Read View
BEGIN;
-- 还没有 Read View
SELECT * FROM user WHERE id = 1; -- → 创建 Read View A
SELECT * FROM user WHERE id = 2; -- → 复用 Read View A
SELECT * FROM user WHERE id = 3; -- → 复用 Read View A
COMMIT;
-- 造成的结果:整个事务看到的是同一时刻的快照
READ COMMITTED
BEGIN;
SELECT * FROM user WHERE id = 1; -- → 创建 Read View A
-- 其他事务提交了修改
SELECT * FROM user WHERE id = 1; -- → 创建 Read View B
-- 同一行两次读取结果可能不同
COMMIT;
Read View 的释放
Read View 在事务提交或回滚时被释放:
- Read View = 快照 = 指向 Undo Log 中某个版本的信息
- Read View 未释放 → 那些版本不能被 Purge 线程清理
- 长事务阻止 Read View 释放,导致 Undo Log 膨胀
面试要点
- Read View 的本质:事务快照时刻的活跃事务"名单"
- 可见性四规则:自己可见 → 已提交可见 → 未开始不可见 → 快照时活跃的不可见
- 与隔离级别的关系:REPEATABLE READ 复用 Read View,READ COMMITTED 重建 Read View
- 面试高频:"Read View 如何决定可见性?"——把四个规则讲清楚即可
- 长事务的代价:Read View 不释放 → 版本无法清理 → Undo 膨胀
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容