硬核图解:MySQL 是如何利用 MVCC + 锁实现“可重复读”的?

硬核图解:MySQL 是如何利用 MVCC + 锁实现"可重复读"的?

在数据库面试或架构设计中,我们经常会被问到一个经典问题:"MySQL 的 RR(Repeatable Read)隔离级别是如何解决不可重复读的?"

很多同学脱口而出:"MVCC!"

这个答案对,但也不全对。MVCC(多版本并发控制)确实是核心,但它解决的是读写并发 时的视图一致性问题。而在真实的业务场景中,数据库必须同时处理并发读并发写 。仅仅依靠 MVCC 是不够的,必须配合锁机制才能构建起完整的隔离性防线。

今天,我们就通过一个真实的"银行转账 + 财务审计"场景,像剥洋葱一样拆解 MySQL 底层是如何通过 MVCC 和锁的协同,来保证数据绝对安全的。

一、 案发现场:转账与审计的并发博弈

为了还原真实场景,我们构建一个简单的账户表 accounts,包含 ID、姓名和余额。

初始数据如下(总余额 3500.00):

id name balance
1 Alice 1000.00
2 Bob 500.00
3 Charlie 2000.00

现在,系统中有两个并发事务正在执行:

  • 事务 T1 (TRX_ID=100):转账业务。Alice 向 Bob 转账 100 元。
  • 事务 T2 (TRX_ID=200):审计业务。查询所有账户的总余额。

我们的目标是观察:在 T1 修改数据但未提交、以及提交后的各个阶段,T2 到底看到了什么?

二、 时序拆解:上帝视角看数据流转

为了讲清楚 MVCC 的工作原理,我们将时间轴拉长,一步步看 MySQL 内部发生了什么。

T1 & T2 事务开启

sql 复制代码
-- 事务 T1 (ID=100)
START TRANSACTION;

-- 事务 T2 (ID=200)
START TRANSACTION;

此时,两个事务都领取到了自己的事务 ID。数据库中的数据尚未变动。

T2 第一次查询:快照生成(Read View)

事务 T2 发起第一次总额查询:

sql 复制代码
SELECT SUM(balance) FROM accounts;

底层发生了什么? 在 RR 隔离级别下,这是 T2 的第一条快照读(Snapshot Read)。MySQL 会在此时此刻为 T2 生成一个 Read View(读视图)。这个视图包含了当前系统中所有"活跃"(未提交)的事务 ID。

  • T2 的 Read View 核心参数
    • m_ids: [100, 200] (当前活跃的事务是 T1 和 T2)
    • min_trx_id: 100 (最小活跃 ID)
    • max_trx_id: 201 (下一个将要分配的 ID)

结果 :T2 看到的数据是初始状态,总额 3500.00

T3 事务 T1 执行转账:版本链诞生

事务 T1 开始干活了,执行转账 SQL:

sql 复制代码
UPDATE accounts SET balance = 900.00 WHERE id = 1;  -- Alice -100
UPDATE accounts SET balance = 600.00 WHERE id = 2;  -- Bob +100

底层发生了什么? 这里涉及到了 MVCC 的核心------Undo Log 版本链。 MySQL 不会直接覆盖旧数据,而是生成新记录,并用回滚指针(Roll_Ptr)指向旧记录。

此时 id=1 (Alice) 的行结构变成了这样:

  1. 最新版本 :Balance=900, DB_TRX_ID=100 (T1的ID)
  2. Undo Log 旧版本 :Balance=1000, DB_TRX_ID=50 (上次修改的事务ID)

同时,T1 会对 id=1 和 id=2 这两行数据加上排他锁(X锁),防止其他事务(如 T3)同时修改。

T4 事务 T2 第二次查询:复用快照

在 T1 修改完但未提交时,T2 再次查询总额:

sql 复制代码
SELECT SUM(balance) FROM accounts;

结果预测 : 如果发生脏读,T2 会算成 3500(900+600+2000)。但在 RR 级别下,结果依然是 3500.00(1000+500+2000)。

为什么?可见性判断算法生效 T2 复用 了 T2 时刻生成的 Read View。当扫描到 Alice 的行(最新 balance=900)时,MySQL 拿着这行的事务 ID (TRX_ID=100) 去问 Read View:

"事务 100 修改的数据,我能看吗?"

Read View 答:

"100 在我的活跃列表 [100, 200] 里。这意味着我创建快照时,它还没提交呢。不可见!"

于是,MySQL 顺着 Undo Log 链条往下找,找到了旧版本(balance=1000, TRX_ID=50)。TRX_ID=50 小于最小活跃 ID,说明早就提交了,可见

同理,Bob 的账户也读取了旧版本。

T5 & T6 T1 提交与 T2 第三次查询

sql 复制代码
-- T1 提交
COMMIT; 

-- T2 第三次查询
SELECT SUM(balance) FROM accounts;

最关键的时刻来了:T1 已经提交了,数据在物理上已经变成了 900 和 600。T2 此时再查,能看到变化吗?

结果 :依然是 3500.00(1000+500+2000)。

这就是 "可重复读"(Repeatable Read) 的含义。 在 RR 级别下,事务只在第一次 SELECT 时生成 Read View,后续所有的查询都复用这个 Read View。 虽然 T1 提交了,但在 T2 的 Read View 眼里,T1 依然属于"创建快照时还未提交的家伙",所以它的修改依然不可见。

三、 技术解密:MVCC 与锁的完美协同

通过上面的流程,我们可以总结出 MySQL 处理并发的核心逻辑:读写分离

这里的"读写分离"不是指主从架构,而是指读操作和写操作使用不同的并发控制机制

1. MVCC:守护"一致性读"

T2 在整个过程中,无论 T1 怎么折腾,无论 T1 是否提交,T2 就像在一个独立的平行宇宙中,看到的数据始终一致。

  • 机制:Read View + Undo Log。
  • 防御:脏读(Dirty Read)、不可重复读(Non-repeatable Read)。

2. 锁:守护"并发写"

在 T1 更新数据的同时,如果有一个事务 T3 也想修改 Alice 的余额:

sql 复制代码
UPDATE accounts SET balance = 800 WHERE id = 1;

T3 会被阻塞。因为 T1 持有了行级锁(Record Lock)。只有 T1 提交释放锁后,T3 才能执行。

  • 机制:行锁(Row Lock)、间隙锁(Gap Lock)。
  • 防御:更新丢失(Lost Update)、写冲突。

3. 为什么是最佳搭档?

如果不使用这套组合拳,我们会陷入两个极端:

  • 没有 MVCC:T2 为了读取数据一致性,必须加读锁(S锁)。这会导致 T1 想转账时被阻塞,读写串行化,性能极差。
  • 没有锁:T1 和 T3 同时修改,发生"更新丢失",钱算错了,这是金融系统的灾难。

MVCC 让读不阻塞写,锁让写不覆盖写。

四、 总结

我们常说的"MySQL 隔离性",在底层其实是由两套机制共同支撑的:

  1. Read View 可见性规则

    python 复制代码
    if trx_id == creator_id:
        return "可见 (自己改的)"
    if trx_id < min_trx_id:
        return "可见 (早就是历史了)"
    if trx_id in active_ids:
        return "不可见 (还在跑着呢)"
  2. 锁机制:确保同一时刻只有一个事务能修改同一行数据,维护数据的物理完整性。

下次再遇到"转账并发"的问题,不要只盯着代码层面的 synchronized 或分布式锁,别忘了数据库底层已经为你筑起了最坚固的防线。

思考题:如果将隔离级别改为 RC(读已提交),T6 时刻 T2 查询的结果会变成多少?为什么?欢迎在评论区讨论。

相关推荐
努力成为包租婆21 分钟前
uniapp--原生插件开发
java·数据库·uni-app
羑悻的小杀马特1 小时前
PostgreSQL + Cpolar 组合拳,彻底打破局域网限制,远程访问数据库像本地一样简单
数据库·postgresql
松涛和鸣1 小时前
DAY61 IMX6ULL UART Serial Communication Practice
linux·服务器·网络·arm开发·数据库·驱动开发
二哈喇子!3 小时前
MySQL数据库概述
mysql
二哈喇子!8 小时前
MySQL数据更新操作
数据库·sql
二哈喇子!8 小时前
MySQL命令行导入数据库
数据库·sql·mysql·vs code
心动啊1218 小时前
SQLAlchemy 的使用
数据库
曾经的三心草9 小时前
redis-2-数据结构内部编码-单线程-String命令
数据结构·数据库·redis
二哈喇子!9 小时前
基于SSM框架的公交车查询系统的设计与实现
java·数据库·ssm
Coder_Boy_10 小时前
基于SpringAI的在线考试系统-智能考试系统-学习分析模块
java·开发语言·数据库·spring boot·ddd·tdd