📌 今日关键词:​MVCC​、InnoDB、并发控制、事务隔离级别、Undo ​Log、Read View、无锁读

大家好,我是​数据库的小学妹​👋

我们之前学过事务和锁,知道锁可以保证并发数据的一致性。但是你有没有想过:

如果每次读数据都要加锁,那高并发下读操作也会互相阻塞,性能岂不是很差?
为什么我们平时写 SELECT 从来不加锁,也不会读到错误的数据?

这就是MVCC(多版本并发控制) 的功劳。它是InnoDB引擎的“秘密武器”,让读操作不加锁也能读到一致性数据,大大提升了并发性能。

今天,我们就来揭开这个让数据库性能起飞的核心黑科技——MVCC(多版本并发控制)。让你理解数据库“无锁读”的魔法!

🧩 一、 什么是MVCC?

MVCC​(Multi-Version Concurrency Control,多版本并发控制) 是一种不用加锁就能实现事务隔离的机制。它的核心思想是:

每次写操作(UPDATEDELETE)不直接覆盖旧数据,而是产生一个新版本。读操作根据事务的隔离级别和时间戳,选择合适的版本来读。

这样一来,读操作永远不用等待写操作,写操作也不用等待读操作,读写互不阻塞。

💡 类比:你在写一份文档,每隔一段时间保存一个历史版本。别人想读的时候,可以选择读最新版,也可以选择读某个历史版。你写你的,他读他的,互不干扰。

⚙️ 二、MVCC的底层原理:三个隐藏字段 + undo log

📍隐藏字段(The Hidden Markers)

InnoDB在每行数据背后,偷偷加了几个“看不见的字段”:

  • DB_TRX_ID​: “出生证明”。记录是哪个事务(Transaction)创建了这一行。
  • DB_ROLL_PTR​: “回溯指针”。指向这条数据修改前的“上一个版本”在哪里(指向Undo Log)。
  • DB_ROW_ID​: 单调递增的行ID(如果没有主键,InnoDB就用这个)。

📍​​Undo ​Log​**(时光隧道)——MVCC的物理实现基础**

  • 每当你更新一行数据,InnoDB不会直接覆盖旧数据,而是把旧数据扔进 Undo Log。
  • 这样,新版本和旧版本就连成了一条“版本链”。最新的数据在链头,越往后越古老。

📍Read View(读视图)——MVCC的“决策大脑”

当执行SELECT查询时,InnoDB会生成一个​**Read View(读视图)​,它就像一个​"快照"**​,记录了当前系统中活跃事务的ID列表。

Read View 包含几个关键信息:

  • m_ids:生成Read View时,所有活跃的(未提交)事务ID列表
  • min_trx_id:m_ids中的最小值
  • max_trx_id:下一个要分配的事务ID

可见性规则(判断版本链中的哪个版本对当前事务可见):

  • 当前版本的事务ID < min_trx_id → 可见(该事务已提交)
  • 当前版本的事务ID > max_trx_id → 不可见(该事务在未来生成,还没发生)
  • 当前版本的事务ID在m_ids中 → 不可见(该事务活跃未提交,应读旧版本)
  • 否则 → 可见

如果当前版本不可见,就通过DB_ROLL_PTR找上一个版本,重复判断,直到找到可见版本。

💡 这就是为什么在可重复读(RR) 级别下,同一个事务多次SELECT看到的数据是一致的——因为只读第一次生成的Read View。而读已提交(RC) 级别每次SELECT都重新生成Read View,所以能看到其他事务已提交的新数据。

🧠 三、 深度解析:为什么它能解决“读写冲突”?

结合我们之前学的“事务隔离级别”,MVCC主要解决了“​读已提交​(Read Committed)”和“​可重复读​(Repeatable Read)”这两个级别下的并发问题。

🔍场景模拟:

假设你在淘宝查看余额(事务A),同时银行正在给你发工资(事务B)。

🔶传统锁机制:

银行发工资时,你的查询必须等待,直到工资发完你才能看到余额。你得干等。

🔷MVCC机制:

  • 你的查询(事务A)开始,生成了一个 Read View。
  • 此时银行(事务B)还没提交,ID在Read View的“黑名单”里。
  • 数据库顺着“版本链”往上找,给你展示的是发工资之前的余额快照。
  • 结果: 你看数据的时候,银行在改数据,我们互不干扰!你看到的是“旧但一致”的数据,而不是错误的中间状态。

⚠️​ 核心误区纠正:

很多人认为MVCC能解决“幻读”。

  • 真相​: 在MySQL的InnoDB中,MVCC配合Next-Key Lock(一种特殊的锁)才能彻底解决幻读。单纯靠MVCC是不够的。这也是为什么我们在之前的“锁机制”文章中提到InnoDB的锁很复杂的原因。

🚀 四、 新手避坑与实战建议

理解了MVCC,作为初级开发者,我们在写SQL时要注意什么?

💢快照读 vs 当前读

类型 语法 读的是什么 是否加锁
快照读 SELECT … 根据隔离级别读对应的历史版本 不加锁
当前读 SELECT … FOR UPDATE 或 SELECT … LOCK IN SHARE MODE 读最新的数据版本 加锁

快照读就是MVCC的体现,不阻塞任何写操作。

当前读需要加锁,确保读到的是最新数据,防止并发修改。

-- 快照读(无锁)
SELECT * FROM users WHERE id = 1;

-- 当前读(加行级排他锁)
SELECT * FROM users WHERE id = 1 FOR UPDATE;

💢Undo ​Log会膨胀

  • 因为MVCC要保留历史版本,所以 Undo Log 会越来越大。
  • 避坑: 如果你的业务中有长事务(一个事务开启很久不提交),数据库为了保证你能读到那个“旧快照”,就必须一直保留那些历史版本,不能清理Undo Log。这会导致数据库空间暴涨,甚至拖垮性能。
  • 想知道你的数据库里有没有‘隐形炸弹’吗?运行这条命令查一查:SHOW ENGINE INNODB STATUS\G,重点关注 TRANSACTIONS 部分。

💢索引维护成本

  • 虽然MVCC让读很快,但频繁的更新(UPDATE)会产生大量的历史版本,占用磁盘空间,并增加垃圾回收(Purge)的负担。
  • 建议: 在高并发写入的场景下,虽然MVCC很牛,但也要控制更新的频率,能用状态机的尽量用状态机,避免无意义的频繁更新。

📌 五、 总结

今天我们把数据库最核心的并发控制机制拆解了一遍:

  1. MVCC 是通过保存数据的多版本来实现并发。
  2. 它依靠 Undo Log 形成版本链,依靠 Read View 来判断可见性。
  3. 它让读操作变得极其轻量,不需要等待写操作,是MySQL高性能的基石。

一句话总结​: MVCC就是数据库里的“平行宇宙”,让你在一个事务里看到一个稳定不变的数据世界,而别人在另一个宇宙里改数据,互不干扰。

👋 我是数据库小学妹一个用设计师思维学数据库的转行人。我们一起,把复杂的技术变得简单有趣!💕


本文示例基于 ​MySQL​​ 8.0 + InnoDB。​MVCC是InnoDB核心特性,理解它对掌握事务隔离级别很有帮助。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐