一、Read View 是什么
当一个事务执行第一个快照读(如 SELECT) 时,InnoDB 会为它创建一个 Read View(读视图)。这个 Read View 包含以下关键信息:
| 字段 | 含义 |
|---|---|
m_ids |
创建 Read View 时,所有活跃(未提交)事务的 ID 列表 |
min_trx_id |
m_ids 中的最小值(即最早开始但未提交的事务 ID) |
max_trx_id |
创建 Read View 时,下一个将要分配的事务 ID(即当前最大已分配 ID + 1) |
creator_trx_id |
当前事务自己的 ID(如果是只读事务,则为 0) |
举个例子:
- 当前已分配事务 ID:100, 101, 102
- 活跃事务(未提交):101, 102
- 新事务 T(ID=103)执行
SELECT→ 创建 Read View:
m_ids = [101, 102]min_trx_id = 101max_trx_id = 104(因为 103 已分配,下一个是 104)creator_trx_id = 103
1. 什么是只读事务
正确理解:"只读事务" ≠ "执行了 SELECT 的事务"
常见误解:
"我写了一个 SELECT 语句,所以这是个只读事务,creator_trx_id = 0。"
真相:
在 InnoDB 中,是否为"只读事务"不是由是否写了 SELECT 决定的,而是由事务是否被显式/隐式标记为"只读"决定的。
creator_trx_id = 0 仅当满足以下条件之一:
情况一:使用 START TRANSACTION READ ONLY
sql
START TRANSACTION READ ONLY;
SELECT * FROM users; -- 这才是真正的"只读事务"
-- 此时 Read View 的 creator_trx_id = 0
情况二:设置会话为只读模式
sql
SET SESSION TRANSACTION READ ONLY;
SELECT * FROM users; -- 自动以只读事务运行
情况三:MySQL 自动优化(5.6+)
- 如果一个事务从开始到结束都只执行
SELECT(无任何写操作), - 并且没有显式开启事务(即 autocommit=1 下的单条 SELECT),
- 那么 InnoDB 可能不会分配事务 ID,也不会创建完整的 Read View,
- 而是直接读取最新已提交的数据 (行为类似
READ COMMITTED)。
但注意:这种情况下甚至可能不创建 Read View,所以谈不上 creator_trx_id = 0,而是压根没事务上下文。
2. 普通 SELECT 的真实情况
情况一:默认 autocommit=1
sql
SELECT * FROM users WHERE id = 1;
- 这是一个自动提交的事务(autocommit transaction)
- InnoDB 会分配一个真实的事务 ID(比如 12345)
- 执行
SELECT时会创建 Read View creator_trx_id = 12345(不是 0!)
验证方法:
sqlSELECT trx_id, trx_state, trx_started FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = CONNECTION_ID();即使只执行
SELECT,你也能看到一个活跃事务记录(短暂存在)。
注意事项:autocommit=ON 下的 SELECT 并不会进入 innodb_trx 表!
关键知识点:在 MySQL 中,只有当事务处于"活动状态(RUNNING)"且未提交时,才会出现在 information_schema.innodb_trx 中。
而普通 SELECT 在 autocommit=ON 模式下是自动提交的,执行完立刻提交,事务生命周期极短,可能在查询 innodb_trx之前就已经结束了。
情况二:如何让 SELECT 的事务"可见"?
你需要一个 持续运行的事务 ,而不是瞬间完成的 SELECT。
方法一:使用 BEGIN 显式开启事务(推荐)
Step 1:在 Session A(主会话)中
sql
-- 开启事务(此时会分配 trx_id)
BEGIN;
-- 执行一个长查询(模拟长时间运行)
SELECT SLEEP(10), id FROM test_version WHERE id = 1;
这个查询会卡住 10 秒,事务一直存在。
Step 2:在 Session B(监控会话)中立即执行
sql
SELECT
trx_id,
trx_state,
trx_started,
trx_mysql_thread_id,
trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started DESC;
会看到类似结果:
| trx_id | trx_state | trx_started | trx_mysql_thread_id | trx_query |
|---|---|---|---|---|
| 42185673 | RUNNING | 2026-01-21 17:30:05 | 123 | SELECT SLEEP(10), id FROM test_version WHERE id = 1 |
成功捕获到事务!
方法二:使用 SET autocommit = OFF + SELECT
Step 1:关闭自动提交
sql
SET autocommit = OFF;
-- 执行 SELECT
SELECT SLEEP(10), id FROM test_version WHERE id = 1;
Step 2:在另一个会话中查看 innodb_trx
sql
SELECT * FROM information_schema.innodb_trx;
同样可以看到事务记录。
二、可见性判断规则
假设当前事务 T 要读取某一行数据,该行的 DB_TRX_ID = X。
InnoDB 按以下顺序判断是否可见:
规则 1:如果 X == creator_trx_id → 可见
- 含义:这行是当前事务自己修改的(还没提交,但自己能看到)
- 场景 :事务内先
UPDATE再SELECT,能读到自己改的数据
规则 2:如果 X < min_trx_id → 可见
- 含义 :这行是在当前 Read View 创建之前就已提交的事务修改的
- 为什么?
因为min_trx_id是最早未提交事务的 ID,
所有< min_trx_id的事务必然已经提交 (否则会在m_ids中)
规则 3:如果 X >= max_trx_id → 不可见
- 含义 :这行是由当前 Read View 创建之后才开始的事务修改的
- 为什么?
max_trx_id是"下一个将分配的事务 ID",
所以X >= max_trx_id表示这个事务在 Read View 创建之后才出现
规则 4:如果 min_trx_id <= X < max_trx_id → 需查 m_ids
- 这表示 X 是 Read View 创建时已经存在的事务
- 再判断 :
- 如果
X ∈ m_ids(X 在活跃事务列表中)→ 不可见(因为对方还没提交) - 如果
X ∉ m_ids(X 不在活跃列表中)→ 可见(说明 X 已经提交了)
- 如果
如果当前版本不可见,就通过
DB_ROLL_PTR找到 Undo Log 中的上一个版本 ,然后重复上述 4 条规则,直到:
- 找到可见版本 → 返回该版本数据
- 遍历完所有旧版本(到达初始插入版本)仍不可见 → 返回空(该行对该事务不存在)
三、示例
场景设定
- 初始数据:
id=1, name="Tom"(由事务 50 插入) - 事务 100:
UPDATE name="Jerry" - 事务 101:
UPDATE name="Alice"(未提交) - 事务 102:执行
SELECT name FROM t WHERE id=1;
此时事务 102 的 Read View:
m_ids = [101](101 未提交)min_trx_id = 101max_trx_id = 103creator_trx_id = 102
数据版本链(从新到旧):
- 最新版 :
name="Alice",DB_TRX_ID=101 - 中间版 :
name="Jerry",DB_TRX_ID=100 - 初始版 :
name="Tom",DB_TRX_ID=50
事务 102 的可见性判断过程:
- 检查最新版(DB_TRX_ID=101) :
101 ∈ m_ids→ 不可见 → 继续找旧版本
- 检查中间版(DB_TRX_ID=100) :
100 < min_trx_id (101)→ 可见!
- 返回结果 :
name="Jerry"
即使事务 101 修改了数据,但因为它未提交,事务 102 看不到 "Alice",而是看到上一个已提交版本 "Jerry"。
四、不同隔离级别的影响
REPEATABLE READ(RR)
- Read View 在事务第一次 SELECT 时创建,并复用整个事务
- 所以多次 SELECT 看到同一快照
READ COMMITTED(RC)
- 每次 SELECT 都创建新的 Read View
- 所以能读到其他事务最新已提交的数据
注意:可见性判断规则本身不变,变的只是 Read View 的创建时机!
五、总结
一行数据对当前事务可见,当且仅当:
它的 DB_TRX_ID 对应的事务,在当前 Read View 创建时已经提交。