Read View 是什么?#
Read View(读视图)是 InnoDB 存储引擎中实现 MVCC(多版本并发控制) 的核心数据结构。
本质#
Read View 本质上是一个事务快照,记录了某个时刻系统中活跃事务的状态,用来判断某条记录的哪个版本对当前事务是可见的。
Read View 的结构#
type ReadView struct {
m_low_limit_id uint64 // 高水位:生成时下一个待分配的事务ID
// >= 此值的事务,对我不可见
m_up_limit_id uint64 // 低水位:活跃事务中最小的事务ID
// < 此值的事务,对我可见
m_creator_trx_id uint64 // 创建此 Read View 的事务ID
m_ids []uint64 // 生成时,系统中所有活跃(未提交)事务的ID列表
}可见性判断规则#
当前事务读取某行数据时,会拿该行版本链上每个版本的 trx_id 与 Read View 比较:
版本的 trx_id
│
├─ < m_up_limit_id → 可见(已提交的老事务)
│
├─ >= m_low_limit_id → 不可见(生成之后才开始的事务)
│
└─ 在中间区间内
├─ 在 m_ids 中 → 不可见(生成时还未提交)
└─ 不在 m_ids 中 → 可见(生成时已提交)Read View 何时创建?#
这取决于事务隔离级别:
| 隔离级别 | Read View 创建时机 |
|---|---|
| RC(读已提交) | 每次 SELECT 都创建一个新的 Read View |
| RR(可重复读) | 事务中第一次 SELECT 时创建,之后复用 |
这就是为什么:
- RC 能读到其他事务已提交的最新数据(不可重复读)
- RR 整个事务期间看到的是同一份快照(可重复读)
与 Undo Log 的关系#
Read View 只是"判官",真正的多版本数据存储在 Undo Log 中。
当前行数据 → trx_id=100(不可见)
│
└──► Undo Log 版本链 → trx_id=80(不可见)
│
└──► trx_id=60(可见,返回此版本)Read View 沿着版本链不断回溯,直到找到第一个可见版本为止。
Read View 伪代码#
package main
type ReadView struct {
// 创建 Read View 时,当前活跃事务列表
MIds []int64
// 活跃事务中最小的事务 id
MinTrxID int64
// 下一个将要分配的事务 id
MaxTrxID int64
// 当前事务自己的 id
CreatorTrxID int64
}
// 判断某个版本的 trxID 对当前 Read View 是否可见
func (rv *ReadView) IsVisible(trxID int64) bool {
// 1. 自己创建/修改的版本,总是可见
if trxID == rv.CreatorTrxID {
return true
}
// 2. 如果 trxID < MinTrxID
// 说明这个版本对应的事务在 Read View 创建前就已经提交
if trxID < rv.MinTrxID {
return true
}
// 3. 如果 trxID >= MaxTrxID
// 说明这个版本对应的事务是在 Read View 创建后才开启的
if trxID >= rv.MaxTrxID {
return false
}
// 4. 如果 trxID 在 [MinTrxID, MaxTrxID) 之间
// 需要判断它是否在活跃事务列表中
if rv.inMIds(trxID) {
// 在活跃列表中,说明创建 Read View 时它还没提交
return false
}
// 不在活跃列表中,说明已经提交
return true
}
func (rv *ReadView) inMIds(trxID int64) bool {
for _, id := range rv.MIds {
if id == trxID {
return true
}
}
return false
}总结#
Read View 就是事务开始读数据时拍的一张"系统活跃事务快照",用它来决定 Undo Log 版本链上哪个历史版本是当前事务该看到的。
InnoDB 的 MVCC 会为事务创建 Read View,用来判断记录版本的可见性。 判断规则是:自己的修改总是可见;若版本的 trx_id 小于 Read View 的 min_trx_id,则说明该版本由快照创建前已提交事务生成,可见; 若 trx_id 大于等于 max_trx_id,则说明该版本由快照创建后才启动的事务生成,不可见; 若 trx_id 位于两者之间,则再判断其是否在 m_ids 活跃事务列表中,在则不可见,不在则可见。