简单易懂的方式理解MVCC--2

MVCC = Undo Log + 版本链 + Read View + 可见性判断 Read View 到底如何判断一个版本对事务可见”
 

对,这一步就是 MVCC 最核心、也是最容易绕的地方

你先不要背那些 m_ids / min_trx_id / max_trx_id,我们用一个具体例子把它彻底搞懂。


1. 先记住:Read View 就是一张“事务时间快照”

假设现在有 4 个事务:

 
事务100:已经提交
事务101:已经提交
事务102:正在执行
事务103:正在执行
 

现在事务 104 开始执行:

 
T104
 ↓
创建 Read View
 

它会记录类似:

 
当前活跃事务:
[102, 103, 104]

最小活跃事务ID:
102

下一个尚未分配的事务ID:
105
 

你可以暂时理解成:

 
             事务ID时间线

100    101    102    103    104    105
 |      |      |      |      |      |
已提交   已提交   活跃   活跃   我自己   未来
                 ↑              ↑
              Read View        事务T104
 

于是 T104 就可以定义一个规则:

哪些事务修改的数据,我能看?哪些不能看?


2. InnoDB 判断一个版本,核心就是看 DB_TRX_ID

每条 InnoDB 记录背后都有一个隐藏的:

 
DB_TRX_ID
 

你可以简单理解成:

“这个版本最后是哪个事务产生的?”

比如:

 
id = 1
price = 3000
DB_TRX_ID = 103
 

说明:

这个 3000 版本是事务 103 修改出来的。

然后 Undo Log 里面还有:

 
3000   ← T103产生
 ↓
2000   ← T102产生
 ↓
1000   ← T101产生
 

于是 T104 查询的时候,就开始判断:

这些版本我到底能不能看?


3. 判断规则其实可以浓缩成一句话

对于事务 T104 的 Read View:

如果这个版本是“我创建 Read View 之前已经提交的事务”产生的,我能看到。

如果是:

创建 Read View 时还没提交的事务产生的,我看不到。

如果是:

创建 Read View 之后才产生的事务,我也看不到。

于是可以把事务分成三类。


4. 第一种:事务 ID < min_trx_id

假设:

 
Read View:

min_trx_id = 102
 

那么:

 
事务100
事务101
 

都满足:

 
trx_id < 102
 

这些事务意味着:

在 Read View 创建之前就已经结束了。

所以它们产生的版本:

 
100 → 可见
101 → 可见
 

5. 第二种:trx_id >= max_trx_id

假设:

 
max_trx_id = 105
 

那么:

 
105
106
107
...
 

这些事务都是:

Read View 创建以后才出现的事务。

所以:

 
trx_id >= 105
        ↓
不可见
 

因为:

“你这个事务是在我拍完照片以后才发生的,我当然看不到。”


6. 第三种:最麻烦的情况

比如:

 
min_trx_id = 102
max_trx_id = 105

活跃事务:
[102, 103, 104]
 

现在有一个版本:

 
DB_TRX_ID = 103
 

它满足:

 
102 <= 103 < 105
 

这时候不能简单说可见或者不可见。

因为:

103 是在 Read View 创建的时候仍然活跃的事务。

所以:

 
103 ∈ m_ids
 

因此:

不可见。


7. 所以你可以把规则记成这个表

假设:

 
min_trx_id = 102
max_trx_id = 105
m_ids = [102,103,104]
 
版本的 DB_TRX_ID判断是否可见
100 < 102 ✅ 可见
101 < 102 ✅ 可见
102 活跃事务 ❌ 不可见
103 活跃事务 ❌ 不可见
104 当前事务 ❌ 不按当前版本看
105 >= 105 ❌ 不可见
106 >= 105 ❌ 不可见

这就是 Read View 的核心。


8. 现在把它放进真正的版本链

假设数据库现在有:

 
当前版本:

price = 3000
DB_TRX_ID = 103
       ↓
Undo
price = 2000
DB_TRX_ID = 102
       ↓
Undo
price = 1000
DB_TRX_ID = 101
 

T104 创建 Read View:

 
min_trx_id = 102
max_trx_id = 105
m_ids = [102,103,104]
 

T104 查询 price


第一步:看到 3000

 
price = 3000
DB_TRX_ID = 103
 

判断:

 
103 在 m_ids 中
 

说明:

T103 在我创建 Read View 的时候还没提交。

所以:

 
3000 ❌ 不可见
 

怎么办?

沿着 Undo Log 往回找。


第二步:找到 2000

 
price = 2000
DB_TRX_ID = 102
 

判断:

 
102 在 m_ids 中
 

还是:

 
❌ 不可见
 

继续沿 Undo Log 找。


第三步:找到 1000

 
price = 1000
DB_TRX_ID = 101
 

判断:

 
101 < min_trx_id(102)
 

所以:

 
1000 ✅ 可见
 

于是:

 
T104最终看到:

price = 1000
 

这就是:

MVCC 通过 Read View + 版本链,找到当前事务能够看到的历史版本。


9. 你现在应该能理解“可重复读”了

假设:

 
T101:price = 1000
 

之后:

 
T102:1000 → 2000
 

然后:

 
T103:2000 → 3000
 

T104 开始:

 
创建 Read View
 

它看到:

 
3000 → T103
2000 → T102
1000 → T101
 

因为:

 
T102、T103
 

在 T104 创建 Read View 时都是活跃事务。

所以:

 
3000 ❌
2000 ❌
1000 ✅
 

最终:

 
T104看到1000
 

10. 这时候你就能理解 Read View 到底是什么了

Read View 不是一份数据副本

它更像一个:

“版本过滤器”。

数据库里可能存在:

 
3000
 ↓
2000
 ↓
1000
 

Read View 并没有复制:

 
1000
 

而是告诉 MVCC:

“按照我的事务时间点,这几个版本哪些可以看。”

然后 MVCC:

 
当前版本
   ↓
Read View判断
   ↓
不可见
   ↓
Undo Log
   ↓
上一个版本
   ↓
Read View判断
   ↓
不可见
   ↓
Undo Log
   ↓
上一个版本
   ↓
Read View判断
   ↓
可见
   ↓
返回
 

11. 最重要的一个面试口诀

你可以记:

小于最小活跃事务ID → 可见

大于等于最大事务ID → 不可见

处于中间 → 看是否在活跃事务列表 m_ids 中

在 m_ids 中 → 不可见

不在 m_ids 中 → 可见

也就是:

 
              DB_TRX_ID
                  │
        ┌─────────┼─────────┐
        ↓         ↓         ↓
     < min      中间      >= max
        │         │         │
        ↓         ↓         ↓
       可见     查 m_ids    不可见
                  │
            ┌─────┴─────┐
            ↓           ↓
          在里面       不在里面
            ↓           ↓
          不可见       可见
 

12. 但是还有一个特别重要的坑

Read View 的具体创建时机,和隔离级别有关。

这正是为什么:

 
READ COMMITTED
 

和:

 
REPEATABLE READ
 

的 MVCC 行为不同。

最简单地记:

 
READ COMMITTED
    ↓
每次快照读都可能创建新的 Read View
    ↓
所以第二次可能看到别人已经提交的数据


REPEATABLE READ
    ↓
第一次快照读建立 Read View
    ↓
后续快照读复用这个 Read View
    ↓
所以多次读取保持一致
 

这一步一旦真正理解,你就把 MVCC → Read View → RR/RC → 不可重复读 全部串起来了。

如果你接下来继续学,我建议下一步专门搞懂 m_ids、min_trx_id、max_trx_id 这三个到底分别是什么,以及为什么 trx_id >= max_trx_id 一定不可见。这是 MySQL 面试里最容易被追问的地方。

 
 
 
 
 
 
 
 
 

posted on 2026-08-12 21:42  日思日睿  阅读(3)  评论(0)    收藏  举报