可重复读和读提交是如何工作的?
可重复读隔离级别是启动事务时生成一个Read View,然后整个事务期间都在用这个Read View.
Read View中MVCC如何工作
Read View创建
事务启动时创建 Read View ,此后整个事务期间复用。其中包含事务 id(creator_trx_id )、活跃事务 id 列表(m_ids )、最小活跃事务 id(min_trx_id )、下一个事务 id(max_trx_id )。
数据可见性判断
读取记录时,对比记录 trx_id 与 Read View 相关值 。若 trx_id 小于 min_trx_id ,记录可见;若 trx_id 在 min_trx_id 和 max_trx_id 之间且在 m_ids 列表中,记录不可见,需沿 undo log 链找旧版本记录 ,直至找到 trx_id 小于 min_trx_id 的记录;若 trx_id 大于等于 max_trx_id ,记录不可见 。
实际案例
可重复读案例
1. 事务启动与 Read View 创建
-
事务 A(id=51)先启动
- 创建 Read View(首次读时生成):
creator_trx_id = 51
(当前事务 ID)m_ids = [51]
(活跃事务仅自身,未提交)min_trx_id = 51
(活跃事务最小 ID)max_trx_id = 52
(下一个待分配事务 ID)
- 创建 Read View(首次读时生成):
-
事务 B(id=52)后启动
- 创建 Read View(首次读时生成):
creator_trx_id = 52
(当前事务 ID)m_ids = [51, 52]
(活跃事务包含 A 和 B,均未提交)min_trx_id = 51
(活跃事务最小 ID 为 A 的 51)max_trx_id = 53
(下一个待分配事务 ID)
- 创建 Read View(首次读时生成):
2. 事务 B 第一次读取数据
- 记录状态:
- 初始记录由
trx_id=50
(已提交)生成,值为balance=100万
。
- 初始记录由
- 可见性判断:
- 记录
trx_id=50
< 事务 B 的 min_trx_id=51 → 属于 “创建 Read View 前已提交的事务”,可见。
- 记录
- 结果:事务 B 读到
balance=100万
。
3. 事务 A 修改记录(未提交)
- 事务 A 执行更新:
- 修改
balance=200万
,生成新记录trx_id=51
(未提交,属于活跃事务 A)。
- 修改
- 此时记录版本:
- 新版本:
trx_id=51
(事务 A 修改,未提交) - 旧版本:
trx_id=50
(已提交)
- 新版本:
4. 事务 B 第二次读取数据
- 可见性判断:
- 新版本
trx_id=51
:- 位于事务 B 的
min_trx_id=51
和max_trx_id=53
之间 - 且在事务 B 的
m_ids=[51,52]
中(事务 A 未提交,仍为活跃事务)
→ 不可见,需追溯 undo 日志。
- 位于事务 B 的
- 追溯旧版本
trx_id=50
:trx_id=50 < min_trx_id=51
→ 可见。
- 新版本
- 结果:事务 B 再次读到
balance=100万
,与第一次结果一致。
重点:整个事务期间都在用一个Read View中
读提交案例
前提条件:
- 表中初始记录
balance = 100万
,由事务trx_id=50
(已提交)生成。 - 事务 A(id=51) 先启动,执行
UPDATE balance SET balance = 200万
(未提交,生成新记录trx_id=51
)。 - 事务 B(id=52) 后启动,执行两次
SELECT balance
操作,每次读取时重新创建 Read View。
1. 事务 B 第一次读取(事务 A 未提交)
- 事务 B 创建第一个 Read View:
creator_trx_id = 52
m_ids = [51, 52]
(活跃事务包含未提交的 A 和当前 B)min_trx_id = 51
,max_trx_id = 53
- 对比记录
trx_id
:- 最新记录
trx_id=51
(事务 A 修改的版本),在min_trx_id(51)~max_trx_id(53)
之间,且在m_ids
中(A 未提交)。 - 规则:不可见,沿
undo log
追溯到trx_id=50
的旧版本。
- 最新记录
- 结果:事务 B 读到
balance=100万
。
2. 事务 A 提交后,事务 B 第二次读取
- 事务 A 执行
COMMIT
:- 事务 A 提交,
trx_id=51
的记录变为 “已提交”,不再属于活跃事务。
- 事务 A 提交,
- 事务 B 创建第二个 Read View:
creator_trx_id = 52
(新创建,与第一次不同)m_ids = [52]
(活跃事务仅当前 B)min_trx_id = 52
,max_trx_id = 53
- 对比记录
trx_id
:- 事务 A 修改的记录
trx_id=51
,小于新 Read View 的min_trx_id(52)
(说明 A 在当前 Read View 创建前已提交)。 - 规则:可见,直接读取
trx_id=51
的新版本。
- 事务 A 修改的记录
- 结果:事务 B 读到
balance=200万
。
核心差异(与可重复读对比)
- Read View 创建时机:读提交每次读都新建 Read View,可重复读仅在第一次读时创建。
- 可见性判断依据:
- 第一次读时,事务 A 未提交(在
m_ids
中),不可见; - 第二次读时,事务 A 已提交(不在新
m_ids
中),可见。
- 第一次读时,事务 A 未提交(在
- 效果:事务 B 两次读取结果不同(
100万 → 200万
),体现 “读提交” 允许读取其他事务已提交的最新数据
重点:每次事务B读取都创建新的Read View