【MySQL篇|第十而二篇】深层次讲解MVCC机制

一、什么是 MVCC

MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种数据库并发控制机制。它的核心思想是:为同一份数据保留多个版本,让读操作和写操作各自访问不同版本的数据,从而在不加锁(或尽量少加锁)的情况下实现并发读写。

在传统的锁机制下,读操作通常需要加共享锁,写操作需要加排他锁,读写之间会互相阻塞。而 MVCC 通过"版本"把读写解耦:读操作读取的是某个时间点的一致性快照,写操作则生成新版本,二者互不干扰。

一句话概括:MVCC 用"空间换并发",通过保存历史版本,让读不加锁、读写不阻塞。

二、MVCC 要解决的核心问题

  • 读写冲突:读操作不应被写操作阻塞,写操作也不应被读操作阻塞。
  • 一致性读:同一事务内多次读取同一数据,结果应保持一致(可重复读)。
  • 并发性能:减少锁竞争,提升高并发场景下的吞吐量。
  • 隔离级别支持:为读已提交(RC)、可重复读(RR)等隔离级别提供实现基础。

三、MVCC 的核心组成

3.1 隐藏版本字段

以 InnoDB 为例,每行记录除了用户定义的列之外,还有几个隐藏字段:

字段 含义
DB_TRX_ID 最近一次插入或修改该行的事务 ID
DB_ROLL_PTR 回滚指针,指向 undo log 中的上一个版本
DB_ROW_ID 行 ID,当表没有主键时用于生成聚簇索引

3.2 undo log 版本链

每次修改数据时,旧版本会被写入 undo log,并通过 DB_ROLL_PTR 串成一条版本链。链头是最新版本,链尾是最早的历史版本。读取时根据可见性规则沿链回溯,找到对当前事务可见的版本。

3.3 Read View(读视图)

Read View 是事务在执行快照读时生成的一份"可见性判断依据",主要包含:

  • m_ids:生成 Read View 时活跃(未提交)的事务 ID 列表。
  • min_trx_id:活跃事务中的最小事务 ID。
  • max_trx_id:生成 Read View 时系统应分配的下一个事务 ID。
  • creator_trx_id:创建该 Read View 的事务 ID。

四、可见性判断规则

拿到某个版本的 DB_TRX_ID 后,按以下顺序判断该版本是否可见:

  1. 若 trx_id == creator_trx_id,说明是自己修改的,可见。
  2. 若 trx_id < min_trx_id,说明该版本在 Read View 生成前已提交,可见。
  3. 若 trx_id >= max_trx_id,说明该版本在 Read View 生成后才开启,不可见。
  4. 若 min_trx_id <= trx_id < max_trx_id:若 trx_id 在 m_ids 中,说明事务仍活跃,不可见;否则可见。

若当前版本不可见,则沿 DB_ROLL_PTR 找到上一个版本,重复上述判断,直到找到可见版本或链结束。

五、快照读与当前读

5.1 快照读(Snapshot Read)

普通的 SELECT 属于快照读,读取的是 Read View 对应的历史版本,不加锁。

sql 复制代码
-- 快照读,读取一致性快照
SELECT * FROM account WHERE id = 1;

5.2 当前读(Current Read)

读取最新已提交版本,并对读取的记录加锁,常见于更新、删除以及加锁查询。

sql 复制代码
-- 当前读,加排他锁
SELECT * FROM account WHERE id = 1 FOR UPDATE;

-- 当前读,加共享锁
SELECT * FROM account WHERE id = 1 LOCK IN SHARE MODE;

-- 更新语句本身也是当前读
UPDATE account SET balance = balance - 100 WHERE id = 1;

六、RC 与 RR 下 Read View 的差异

  • 读已提交(RC):每次快照读都会重新生成 Read View,因此能看到其他事务已提交的最新数据,可能出现不可重复读。
  • 可重复读(RR):只在事务中第一次快照读时生成 Read View,后续复用,因此同一事务内多次读取结果一致。

这也解释了为什么 InnoDB 在 RR 隔离级别下能避免不可重复读,但幻读仍需依靠间隙锁(Next-Key Lock)配合解决。

七、MVCC 的使用方式与实践建议

7.1 合理选择隔离级别

大多数业务场景使用默认的 RR 即可;对一致性要求不高、追求更高并发的场景可考虑 RC。切换隔离级别会影响 Read View 的生成时机,需要结合业务评估。

7.2 避免长事务

长事务会导致 undo log 无法及时清理,版本链变长,回滚段膨胀,同时影响 purge 线程回收历史版本。建议:

  • 控制事务粒度,避免在事务中执行耗时操作(如远程调用、大批量计算)。
  • 为事务设置合理的超时时间。
  • 监控 information_schema.innodb_trx 中的长事务。

7.3 区分快照读与当前读

在需要读取最新数据并保证一致性的场景(如扣减库存),应使用当前读或加锁语句,避免基于过期快照做业务判断。

sql 复制代码
-- 扣减库存:使用当前读保证读到最新值
START TRANSACTION;
SELECT stock FROM product WHERE id = 100 FOR UPDATE;
UPDATE product SET stock = stock - 1 WHERE id = 100 AND stock > 0;
COMMIT;

7.4 注意 MVCC 不能完全替代锁

MVCC 主要解决读写冲突,写写冲突仍需依靠行锁。对于唯一性约束、范围更新等场景,仍需理解锁机制,避免死锁和幻读问题。

八、MVCC 的优缺点

维度 说明
优点 读不加锁、读写不阻塞,显著提升并发读性能;天然支持一致性读
优点 减少锁竞争,降低死锁概率
缺点 需要额外存储空间保存历史版本
缺点 版本链过长会增加读取开销,需要 purge 机制回收
缺点 实现复杂,对长事务敏感

九、总结

MVCC 通过隐藏版本字段、undo log 版本链和 Read View 三部分协作,实现了"读不加锁、读写不阻塞"的一致性读能力。理解可见性判断规则、快照读与当前读的区别,以及 RC 与 RR 下 Read View 的生成时机,是掌握 MVCC 的关键。在实际使用中,应重点关注隔离级别选择、长事务治理和写写冲突的锁处理,才能充分发挥 MVCC 的并发优势。

相关推荐
一技安身2 小时前
MySQL 8.4.13 LTS 通用二进制包(Linux免安装版)完整教程
linux·mysql·adb
代码派2 小时前
业务不停机迁移 MySQL 到 TiDB,NineData 数据增量同步实操演示
数据库·mysql·tidb·ninedata·无停机迁移·增量迁移·全量迁移
我不会起名字3222 小时前
MySQL 8.0 迁移后 SQL 变慢:执行计划里 3 类被忽略的成本差异
mysql·explain·执行计划·索引下推·成本模型
Decay__8 小时前
05_MySQL 常用 SQL 大全 —— 诊断、管理、优化辅助 SQL 与三大系统库速查
运维·数据库·sql·mysql·架构
Decay__14 小时前
04_MySQL DBA 日常运维手册 —— 会话管理、锁与阻塞、事务监控、慢查询与日志巡检
运维·数据库·sql·mysql·架构
后台模板学习21 小时前
MongoDB 与 MySQL 在 IM 即时聊天场景中该
数据库·mysql·mongodb
IT大白鼠1 天前
渗透测试实战从靶场搭建到渗透流程——第 8 篇 · 服务型靶标(上):MySQL 双版本写文件链
数据库·mysql·渗透测试
少年的范儿1 天前
Windows 安装 MySQL 8.4 教程(ZIP 解压版):配置 my.ini、初始化数据库与启动服务
数据库·windows·mysql
神仙别闹1 天前
基于Python+MySQL实现(Web)个人博客系统
python·mysql·php