RSS
菜单
全部文章快讯开发科技深度热点

第 6 章 MVCC原理面试精讲

内容摘要

MVCC(多版本并发控制)是 InnoDB 高并发的基石,也是中高级面试的分水岭。本篇用 16 道高频题讲清 MVCC 的三大组件——隐藏字段、undo log 版本链、ReadView,把"快照读为什么不加锁""RC 和 RR 为什么可见性不同""长事务为什么让 undo 暴涨"一次讲透。学完本篇,你能手绘一条记录的版本链,并说出一个事务读到某版本的完整判定过程。前置知识:第 3、4、5 章事务、隔离级别、锁;本系列示例基于 MySQL 8.0。

📖 本篇简介:MVCC(多版本并发控制)是 InnoDB 高并发的基石,也是中高级面试的分水岭。本篇用 16 道高频题讲清 MVCC 的三大组件——隐藏字段、undo log 版本链、ReadView,把"快照读为什么不加锁""RC 和 RR 为什么可见性不同""长事务为什么让 undo 暴涨"一次讲透。学完本篇,你能手绘一条记录的版本链,并说出一个事务读到某版本的完整判定过程。前置知识:第 3、4、5 章事务、隔离级别、锁;本系列示例基于 MySQL 8.0。

目录

  • 1 MVCC 高频考点地图
  • 题目 1什么是 MVCC它解决什么问题
  • 题目 2MVCC 的三大组件是什么
  • 题目 3InnoDB 行的隐藏列有哪些分别干什么
  • 题目 4undo log 版本链是怎么串起来的
  • 题目 5ReadView 是什么结构包含哪些字段
  • 题目 6ReadView 的可见性判定规则是什么
  • 题目 7RC 和 RR 的 ReadView 生成时机有什么不同
  • 题目 8MVCC 怎么做到读不阻塞写写不阻塞读
  • 题目 9快照读和当前读在 MVCC 里的角色
  • 题目 10MVCC 找历史版本的完整过程
  • 题目 11MVCC 的代价是什么
  • 题目 12聚簇索引和二级索引的 MVCC 有何不同
  • 题目 13为什么长事务会让 undo log 暴涨
  • 题目 14MVCC 能完全代替加锁吗
  • 题目 1580 的 MVCC 相比 57 有什么改进
  • 题目 16怎么实验验证 MVCC 在工作
  • MVCC 速记口诀
  • 常见坑与注意事项
  • 复习清单
  • 练习题

1 MVCC 高频考点地图

| 难度 | 典型问题 | 本篇对应 |

|---|---|---|

| 入门 | MVCC 是什么?解决什么? | 题目 1 |

| 基础 | 三大组件?ReadView 结构? | 题目 2、3、5 |

| 进阶 | 可见性判定?版本链? | 题目 6、10 |

| 实战 | undo 暴涨?快照读 vs 当前读? | 题目 9、13 |

一条主线记住:MVCC = 隐藏列记版本 + undo log 串历史 + ReadView 挑可见版本。读不加锁,靠"挑历史版本"实现并发。

题目 1:什么是 MVCC?它解决什么问题?

考点

  • Multi-Version Concurrency Control;
  • 多版本 + 快照读;
  • 读写并发不互斥。

参考答案

  • MVCC(Multi-Version Concurrency Control,多版本并发控制):InnoDB 通过为每行数据维护多个历史版本,让读操作读到"某一时刻的快照",而不用加锁等待写;
  • 解决的核心问题:

1. 读写并发:读不阻塞写、写不阻塞读,大幅提升吞吐;

2. 实现 RC/RR 隔离级别:靠 ReadView 决定看到哪个版本;

  • 没有 MVCC 的数据库(如 MyISAM),读写互相阻塞,并发上不去。
-- MVCC 带来的效果:会话 A 在改,会话 B 读不阻塞
-- A:
BEGIN; UPDATE account SET balance=1 WHERE id=1;   -- 未提交
-- B(普通 SELECT,不加锁):
SELECT balance FROM account WHERE id=1;   -- 读到旧值,不被 A 阻塞

追问

  • 问:MVCC 是所有存储引擎都有吗?答:InnoDB 有;MyISAM/Memory 没有。
  • 问:MVCC 和快照隔离什么关系?答:MVCC 是实现手段,快照隔离是它提供的隔离级别语义。

题目 2:MVCC 的三大组件是什么?

考点

  • 隐藏字段;
  • undo log 版本链;
  • ReadView。

参考答案

MVCC 由三部分组成:

1. 隐藏字段:每行自带 DB_TRX_ID(最后修改的事务 ID)、DB_ROLL_PTR(指向 undo log 的指针);

2. undo log 版本链:每次 UPDATE 都把旧值写到 undo log,用 roll_ptr 串成链表;

3. ReadView:事务读数据时生成的"快照",记录当时活跃事务列表,用来判断哪个版本可见。

  • 一句话:隐藏字段记当前版本,undo log 串历史,ReadView 挑可见版本。

追问

  • 问:DELETE 也进版本链吗?答:InnoDB 的 DELETE 实际是打删除标记(update 一个删除位),也写 undo,所以能回滚和 MVCC。
  • 问:INSERT 的历史版本?答:INSERT 的 undo 在事务提交后即可purge(删除时才用得到),不像 UPDATE 要长期保留。

题目 3:InnoDB 行的隐藏列有哪些?分别干什么?

考点

  • DB_TRX_ID:6 字节,最后修改事务 ID;
  • DB_ROLL_PTR:7 字节,回滚指针;
  • DB_ROW_ID:6 字节,无主键时用。

参考答案

InnoDB 每行有三个隐藏列(用户看不见):

| 隐藏列 | 大小 | 作用 |

|---|---|---|

| DB_TRX_ID | 6 字节 | 最后一次 INSERT/UPDATE 这行的事务 ID |

| DB_ROLL_PTR | 7 字节 | 回滚指针,指向 undo log 中这行的上一个版本 |

| DB_ROW_ID | 6 字节 | 没有主键也没有唯一索引时,InnoDB 自动生成的行 ID |

  • DB_TRX_ID 是 MVCC 判断"这个版本是谁改的"的依据;
  • DB_ROLL_PTR 把当前行和 undo 里的旧版本串起来,形成版本链;
  • 有主键时 DB_ROW_ID 不出现(主键就是聚簇索引)。
-- 普通 SELECT 看不到隐藏列,但可以用 innodb_row_ops 间接观察
-- 实际工程中不需要手动管,理解原理即可
SELECT * FROM information_schema.INNODB_TRX;   -- trx_id 即事务 ID,对应 DB_TRX_ID

追问

  • 问:事务 ID 是全局递增的吗?答:是,InnoDB 全局 trx_sys 分配,单调递增,所以 trx_id 大小可以代表"时间先后"。
  • 问:DB_TRX_ID 会不会溢出?答:6 字节理论上很大,实践中不会;InnoDB 有 wrap-around 处理。

题目 4:undo log 版本链是怎么串起来的?

考点

  • 每次 UPDATE 写一条 undo;
  • roll_ptr 指向前一版本;
  • 链头是最新版本。

参考答案

  • 一条记录被 UPDATE 多次后:

1. 当前行的 DB_TRX_ID = 最新事务 ID,DB_ROLL_PTR 指向 undo log 里的"上一版本";

2. undo log 里那个旧版本也有自己的 DB_TRX_ID 和 DB_ROLL_PTR,再指向更早版本;

3. 这样串成一条从新到旧的版本链;

  • 读数据时,从当前行开始沿链找,找第一个"对当前 ReadView 可见"的版本。
当前行 (trx_id=300, roll_ptr→undo B)
undo B (trx_id=200, roll_ptr→undo A)
undo A (trx_id=100, roll_ptr=null)
  • 事务 trx_id=250 的 ReadView 来读:当前行 trx_id=300 不可见 → 沿链到 B(trx_id=200,可见)→ 读到 B。

追问

  • 问:版本链会无限长吗?答:不会,purge 线程把"没有任何 ReadView 需要的旧版本"清理掉。
  • 问:INSERT 的 undo 在链上吗?答:INSERT 的 undo 只在事务回滚时用,提交后就标记可 purge,不在 MVCC 版本链里长期挂着。

题目 5:ReadView 是什么结构?包含哪些字段?

考点

  • m_ids:生成时活跃事务 ID 列表;
  • min_trx_id / max_trx_id;
  • creator_trx_id。

参考答案

ReadView 是事务读数据时拍的"快照",核心字段:

| 字段 | 含义 |

|---|---|

| m_ids | 生成 ReadView 时,当前活跃(未提交)事务的 ID 列表 |

| min_trx_id | m_ids 里的最小值(最早活跃事务) |

| max_trx_id | 生成 ReadView 时 InnoDB 即将分配的下一个事务 ID |

| creator_trx_id | 生成这个 ReadView 的事务自己的 ID |

  • 判断一个版本对当前事务可不可见,就看它的 DB_TRX_ID 落在哪个区间。

追问

  • 问:max_trx_id 是"当前最大活跃事务 ID"吗?答:不是,是"下一个将分配的 ID",即所有 ≥ max_trx_id 的事务都发生在 ReadView 之后。
  • 问:ReadView 占内存大吗?答:很小,就是一个事务 ID 列表,8.0 优化后更轻。

题目 6:ReadView 的可见性判定规则是什么?

考点

  • 四个判断分支;
  • 沿版本链找第一个可见版本。

参考答案

对某版本的 trx_id,判定:

1. trx_id == creator_trx_id:自己改的,可见;

2. trx_id < min_trx_id:生成 ReadView 前已提交,可见;

3. trx_id >= max_trx_id:生成 ReadView 后才开始的事务,不可见;

4. min_trx_id ≤ trx_id < max_trx_id:看 trx_id 在不在 m_ids 里——在(当时活跃)→ 不可见;不在(当时已提交)→ 可见。

  • 不可见就沿 DB_ROLL_PTR 找 undo log 里的上一个版本,重复判断,直到找到可见版本。
trx_id=300 版本:
  creator=250 → 自己,可见
  min=200, max=400, m_ids={200,300}
  - trx_id=100 < min=200 → 可见(老事务已提交)
  - trx_id=250 == creator → 可见
  - trx_id=350 ≥ max=400?不,350<400 → 在 m_ids?不在 → 可见(已提交)
  - trx_id=300 在 m_ids → 不可见,沿 roll_ptr 找上一版本

追问

  • 问:如果链上所有版本都不可见怎么办?答:不会,链头前的 INSERT 版本一定可见(它在 ReadView 之前已提交)。
  • 问:删除标记的行怎么处理?答:最后一个版本是"删除标记",不可见时查询返回空。

题目 7:RC 和 RR 的 ReadView 生成时机有什么不同?

考点

  • RC 每条 SELECT 新建 ReadView;
  • RR 第一次 SELECT 建一次复用。

参考答案

  • RC(READ COMMITTED):每执行一条普通 SELECT 都生成新的 ReadView,所以能看到别的事务最新提交;
  • RR(REPEATABLE READ):事务启动后第一次普通 SELECT 时生成 ReadView,整个事务复用同一个,之后都看不到新提交;
  • 这就是 RC"不可重复读"、RR"可重复读"的根因。
-- RR:ReadView 在第一次 SELECT 时固定
BEGIN;
SELECT ...;   -- ← 此刻生成 ReadView
SELECT ...;   -- 复用,结果相同
COMMIT;

-- RC:每条 SELECT 新 ReadView
BEGIN;
SELECT ...;   -- 新 ReadView
SELECT ...;   -- 又一个新 ReadView,可能看到新提交

追问

  • 问:BEGIN 立刻生成 ReadView 吗?答:不,第一次普通 SELECT 才生成;START TRANSACTION WITH CONSISTENT SNAPSHOT 才立刻生成。
  • 问:当前读走 ReadView 吗?答:不走,当前读读最新版本 + 加锁,不受 ReadView 限制。

题目 8:MVCC 怎么做到"读不阻塞写、写不阻塞读"?

考点

  • 读走快照不加锁;
  • 写加行锁但不影响读旧版本;
  • 吞吐提升。

参考答案

  • 读(普通 SELECT):走 MVCC 读快照,不加任何锁,所以不阻塞写;
  • 写(UPDATE/DELETE):对当前行加 X 锁,但其他事务的普通 SELECT 仍能读 undo log 里的旧版本,不阻塞读;
  • 对比没有 MVCC 的系统:读写互相阻塞,并发上不去;
  • 这就是 InnoDB 高并发的核心。

追问

  • 问:写会阻塞写吗?答:会,同一行两个 UPDATE 要排队,靠 X 锁。
  • 问:当前读(FOR UPDATE)还享受 MVCC 吗?答:不享受,它读最新版本并加锁,属于"读也要锁"。

题目 9:快照读和当前读在 MVCC 里的角色?

考点

  • 快照读 = MVCC;
  • 当前读 = 最新版本 + 锁;
  • 混用的坑。

参考答案

  • 快照读:普通 SELECT,走 MVCC 读 ReadView 选定的版本,不加锁;
  • 当前读:SELECT ... FOR UPDATE/FOR SHARE、UPDATE、DELETE,读最新已提交版本并加锁,不走 ReadView;
  • 两者并存是为了业务灵活性:查询用快照读性能好,"读了就要改"用当前读保证不丢更新;
  • 混用会出现"逻辑幻读":事务内先快照读看不到新行,再当前读看到了(见第 4 章题目 9)。
BEGIN;
SELECT balance FROM account WHERE id=1;   -- 快照读,100(MVCC)
-- 别的事务改成 50 提交
SELECT balance FROM account WHERE id=1;   -- 快照读,还是 100
SELECT balance FROM account WHERE id=1 FOR UPDATE;  -- 当前读,50(最新)
COMMIT;

追问

  • 问:UPDATE 是当前读,那它看到的旧版本会被回滚吗?答:UPDATE 基于最新值改,会写新 undo;事务回滚时用 undo 反向恢复。
  • 问:快照读能看到自己事务所做的修改吗?答:能,ReadView 把 creator_trx_id 算可见,这就是"写后读一致"。

题目 10:MVCC 找历史版本的完整过程?

考点

  • 沿版本链;
  • 每步用 ReadView 判定;
  • 找到即停。

参考答案

完整过程:

1. 从聚簇索引读到当前行,取它的 DB_TRX_ID;

2. 用 ReadView 判定该版本可见性:

  • 可见 → 返回这一行;
  • 不可见 → 顺着 DB_ROLL_PTR 找到 undo log 里的上一个版本;

3. 对上个版本重复步骤 2,直到找到第一个可见版本;

4. 如果链上最后是"删除标记"且不可见,返回该行不存在。

  • 整个过程不需要加锁,代价是可能要沿链多读几页 undo log。

追问

  • 问:版本链很长会不会慢?答:长事务下版本链可能很长,读旧版本要多跳几次 undo;这也是长事务危害之一。
  • 问:purge 后旧版本没了怎么办?答:purge 只清"没有任何 ReadView 需要的版本",正在活跃的 ReadView 对应的版本不会被清。

题目 11:MVCC 的代价是什么?

考点

  • 存储开销:undo log 占空间;
  • CPU:可见性判断;
  • 长事务拖 purge。

参考答案

  • 存储开销:每行多 6+7 字节隐藏列,undo log 占用大量表空间;
  • CPU 开销:每次快照读都要沿版本链做可见性判断;
  • 长事务放大问题:长事务是"最旧活跃 ReadView",purge 不能清它之前的 undo,导致 undo 暴涨、ibdata 膨胀;
  • 收益(高并发)远大于代价,这是 InnoDB 默认架构。
-- 观察 undo 堆积:History list length
SHOW ENGINE INNODB STATUS\G
-- 关注 TRANSACTIONS 段的 History list length,持续上涨 = 有长事务

追问

  • 问:undo 暴涨磁盘满了怎么办?答:找长事务 KILL 掉,purge 会追上;8.0 独立 undo 表空间可自动 truncate。
  • 问:MVCC 能关掉吗?答:不能,它是 InnoDB 核心机制;隔离级别降到 RU 只是 ReadView 简化,不是关 MVCC。

题目 12:聚簇索引和二级索引的 MVCC 有何不同?

考点

  • 二级索引叶子存主键;
  • 二级索引的 undo 链通过 roll_ptr 回聚簇索引;
  • 回表时再走 MVCC。

参考答案

  • 聚簇索引:叶子直接是整行,有完整隐藏列和 roll_ptr,MVCC 版本链直接在聚簇索引上串;
  • 二级索引:叶子存主键值,没有自己的 roll_ptr。二级索引记录的"旧版本"怎么找?

1. 二级索引页里也有一个隐藏的 trx_id 和 roll_ptr(8.0 叫 secondary index undo log);

2. 找不到对应版本时,先拿到主键值,回聚簇索引走完整版本链;

  • 所以二级索引的 MVCC 比聚簇索引复杂,开销也略大。
-- 二级索引查找后回表,回表时再做 MVCC 判定
-- SELECT * FROM t WHERE name='tom';
-- 1. 二级索引 idx_name 找到 name='tom' 的主键 id=10
-- 2. 用 id=10 回聚簇索引,沿版本链找可见版本

追问

  • 问:覆盖索引还需要回表做 MVCC 吗?答:覆盖索引要的列都在二级索引里,不回聚簇索引,直接在二级索引上做可见性判断,性能更好。
  • 问:8.0 对二级索引 MVCC 有优化吗?答:8.0 改进了 secondary index 的 undo 处理,减少回表。

题目 13:为什么长事务会让 undo log 暴涨?

考点

  • purge 水位线 = 最旧活跃 ReadView;
  • 长事务拖住水位;
  • 解决:kill 长事务。

参考答案

  • purge 线程清理 undo log 的前提:这个旧版本对所有活跃事务都不可见了;
  • "最旧活跃事务"决定了水位线——只要有一个长事务还在跑,它之前产生的所有 undo 都不能清;
  • 结果:undo log 越堆越多,表空间膨胀,查询变慢;
  • 治理:监控 INNODB_TRX 的 trx_started,跑超过 1 分钟的事务重点排查。
SELECT trx_id, trx_started,
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS run_sec,
       trx_query
FROM information_schema.INNODB_TRX
ORDER BY trx_started;   -- 最老的排在前面

追问

  • 问:只读事务也算长事务吗?答:算,它也有 ReadView,拖住 purge;8.0 对只读事务有优化但仍要控时长。
  • 问:怎么定位是谁拖住的?答:找 trx_started 最早的那个事务,看 trx_mysql_thread_id,KILL 掉。

题目 14:MVCC 能完全代替加锁吗?

考点

  • 快照读无锁;
  • 当前读/写必须加锁;
  • 一致性写需要锁。

参考答案

  • 不能完全代替:

1. 读:普通 SELECT 走 MVCC 无锁,够了;

2. 写:UPDATE/DELETE 必须加 X 锁,否则两个事务并发写同一行会丢更新;

3. 读了就要改:必须 SELECT ... FOR UPDATE 当前读 + 加锁,否则 MVCC 读到旧值改上去会覆盖别人;

  • MVCC 解决的是"读写并发",写写并发还是要靠锁。
-- 反例:靠 MVCC 读然后改,丢更新
-- A:
SELECT balance FROM account WHERE id=1;   -- 100(MVCC)
-- B:
SELECT balance FROM account WHERE id=1;   -- 100
UPDATE account SET balance=50 WHERE id=1; -- B 提交
-- A:
UPDATE account SET balance=balance-10 WHERE id=1;  -- 基于旧值,覆盖 B 的修改
-- 正解:SELECT ... FOR UPDATE 加锁

追问

  • 问:那为什么不所有读都 FOR UPDATE?答:那样读写互斥,并发暴跌,MVCC 的意义就没了。
  • 问:CAS 乐观锁算 MVCC 吗?答:不算,它是应用层版本号校验,不加 DB 锁,但思想类似"多版本"。

题目 15:8.0 的 MVCC 相比 5.7 有什么改进?

考点

  • ReadView 性能优化;
  • 历史列表 purge 更积极;
  • 事务 ID 缓存。

参考答案

  • 8.0 对 MVCC 的改进:

1. ReadView 创建开销降低:只读事务的 ReadView 不再复制整个活跃事务列表;

2. purge 更并行:多线程 purge,undo 清理更快;

3. transaction ID 改进:避免 64 位 trx_id 回绕问题;

4. 变更缓冲(change buffer)优化:二级索引写性能提升;

  • 核心机制(隐藏列 + undo 链 + ReadView)和 5.7 一致,面试不用背具体参数。

追问

  • 问:8.0 默认隔离级别变了吗?答:还是 RR,没变。
  • 问:8.0 的 undo 能自动 truncate 吗?答:可以,innodb_undo_log_truncate=ON,独立 undo 表空间自动收缩,5.7 要手工。

题目 16:怎么实验验证 MVCC 在工作?

考点

  • 两个会话对比;
  • 普通 SELECT 看不到未提交;
  • FOR UPDATE 看得到最新。

参考答案

用两个会话验证(本示例基于 MySQL 8.0,先 SET SESSION transaction_isolation='REPEATABLE-READ'):

-- 会话 A:
BEGIN;
UPDATE account SET balance=1 WHERE id=1;   -- 未提交,不 COMMIT
-- 会话 B(另一连接):
SELECT balance FROM account WHERE id=1;    -- 看到旧值(MVCC 读 undo 快照),不阻塞
-- 会话 A:COMMIT;
-- 会话 B 再普通 SELECT:RR 下仍看到旧值(ReadView 固定)
-- 会话 B:SELECT balance FROM account WHERE id=1 FOR UPDATE;  -- 看到最新 1
  • 现象解释:B 的普通 SELECT 走 MVCC,A 未提交时读旧版本;A 提交后,B 在 RR 下 ReadView 固定仍读旧值;FOR UPDATE 当前读才看到最新。

追问

  • 问:怎么看版本链?答:工程上不直接看,理解原理即可;innodb_ruby 等工具可解析页结构。
  • 问:MVCC 对性能影响多大?答:读性能接近 MyISAM,写性能略低于纯锁,但综合并发远好。

MVCC 速记口诀

| 组件 | 一句话 |

|---|---|

| 隐藏列 | trx_id 记谁改的,roll_ptr 指向上一版本 |

| undo 版本链 | 新 → 旧,沿 roll_ptr 找历史 |

| ReadView | 拍快照,记当时活跃事务列表 |

| 可见性 | 自己改的可见;< min 可见;≥ max 不可见;中间看在不在活跃列表 |

| RC 生成时机 | 每条 SELECT 新建 |

| RR 生成时机 | 第一次 SELECT 建一次复用 |

| 快照读 | 普通 SELECT,走 MVCC 不加锁 |

| 当前读 | FOR UPDATE/UPDATE,读最新加锁 |

| purge 水位 | 最旧活跃事务,长事务拖住不释放 |

常见坑与注意事项

1. 以为 MVCC 能防写写并发:不能,写还是要加 X 锁。

2. 以为 RR 下所有读都一致:当前读读最新,混用会"逻辑幻读"。

3. 长事务不自知:RR 下一个大 SELECT 跑 10 分钟,undo 涨几个 G。

4. undo 暴涨不敢动:找到最老活跃事务 KILL 掉,purge 会追上;不要直接清 ibdata。

5. 以为覆盖索引不做 MVCC:覆盖索引在二级索引上做可见性判断,不回聚簇索引。

6. RC 下以为 ReadView 一次就好:RC 每条 SELECT 新建,这是它和 RR 的本质区别。

7. 把 MVCC 和乐观锁混为一谈:MVCC 是数据库多版本,乐观锁是应用层 version 字段,思想类似但实现不同。

复习清单

| 题目 | 核心结论 | 易错点 |

|---|---|---|

| MVCC 定义 | 多版本 + 快照读,读写不互斥 | 说"加锁读" |

| 三大组件 | 隐藏列 + undo 链 + ReadView | 漏一个 |

| 隐藏列 | trx_id、roll_ptr、row_id | 大小记不清没关系,作用要讲对 |

| 版本链 | roll_ptr 串 undo,新→旧 | 方向说反 |

| ReadView | m_ids/min/max/creator | max_trx_id 是"下一个将分配" |

| 可见性 | 自己/已提交可见,活跃/未来不可见 | 判定分支漏 |

| RC vs RR | ReadView 生成时机不同 | 答成"事务开始生成" |

| 快照/当前读 | 普通 SELECT 走 MVCC,FOR UPDATE 不走 | 混淆 |

| undo 暴涨 | 长事务拖住 purge 水位 | 只读事务不算 |

| MVCC vs 锁 | 读无锁,写仍要锁 | 以为 MVCC 替代锁 |

练习题

练 1:事务 T(trx_id=500)生成 ReadView,当时活跃事务为 {200, 300},下一个将分配 trx_id=600。某记录当前版本 trx_id=400,它对 T 可见吗?如果 trx_id=250 呢?

<details>

<summary>参考答案</summary>

  • trx_id=400:min=200, max=600,200≤400<600,看是否在 m_ids={200,300}——不在,说明 400 在 ReadView 生成前已提交,可见。
  • trx_id=250:同样在 [200,600) 区间,不在 m_ids,已提交,可见。
  • 如果 trx_id=300(在 m_ids),不可见,要沿 roll_ptr 找更早版本。

</details>

练 2:为什么 mysqldump --single-transaction 导大库时不能有长事务跑在业务上?

<details>

<summary>参考答案</summary>

--single-transaction 在 RR 下用一个长 ReadView 保证导出一致性;如果业务上还有更长的事务,purge 水位被业务长事务拖住,undo 在 dump 期间暴涨。反过来 dump 本身也是长事务,业务上跑大 UPDATE 会让 undo 链很长。所以大库导出要在低峰期、业务低负载时做。

</details>

练 3:RR 下事务 A 第一次 SELECT 读到 balance=100;事务 B 把它改成 50 并提交;事务 A 第二次普通 SELECT 读到多少?改成 SELECT ... FOR UPDATE 又读到多少?为什么?

<details>

<summary>参考答案</summary>

第二次普通 SELECT 读到 100:A 的 ReadView 在第一次 SELECT 时固定,B 的 trx_id 在 m_ids 生成时未活跃或不在可见范围,沿版本链读到 trx_id 更早的 100 版本。

改成 FOR UPDATE 是当前读,读最新已提交值 50,并加 X 锁。

这就是 MVCC 快照读与当前读的差异。

</details>

下一篇预告:第 7 章 InnoDB架构面试精讲

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 6 章 MVCC原理面试精讲》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。