🎬 数据库界的“平行宇宙”:MySQL 怎么用 MVCC 让读写不掐架?

同事灵魂拷问:“为啥我事务里查不到别人刚提交的数据?”
我嘬了口冰美式:“因为 MySQL 偷偷给你开了个时光机啊。”
同事:“???数据库还能拍科幻片?”

今天咱就搬好小板凳,唠唠 InnoDB 是怎么靠 MVCC 这招“分身术”,让多人同时读写不抢道、不锁死的。保证不催眠,备好瓜子 🍉,咱们发车!


先上张"全家福"

🛡️ 事务ACID怎么保障的?

⚛️ 原子性
要么全成,要么全不成

📝 Undo Log
记旧值,回滚用

💾 持久性
提交了就不丢

🔄 Redo Log
WAL机制

🔒 隔离性
事务之间不干扰

🔐 锁机制

📸 MVCC
多版本并发控制

✅ 一致性
数据总是对的

一句话总结:Undo Log 管回滚,Redo Log 管持久化,锁+MVCC 管隔离。今天重点聊 MVCC 这个"黑科技"。


🛡️ 事务 ACID 的“四大护法”

事务想稳稳拿捏 ACID(原子、一致、隔离、持久),靠的是后台四位老大哥:

  • 📝 Undo Log(后悔药):专记旧值,随时能让你“撤回上一步”。
  • 🔄 Redo Log(防丢笔记):WAL 机制,数据先写日志再刷盘,断电了也能靠它“读档复活”。
  • 🔐 锁机制(交通警):管写冲突,红灯停绿灯行,别抢道。
  • 📸 MVCC(端水大师):读不阻塞写,写不阻塞读,大家各看各的,世界和平。

💡 一句话人话总结:Undo 管后悔,Redo 管保命,锁管打架,MVCC 管“各拍各的照”。今天咱就扒开 MVCC 的底裤 👖!


📸 MVCC 到底是个啥?

全称 Multi-Version Concurrency Control,翻译成人话就是:多版本并发控制
简单说:你改数据,MySQL 不直接覆盖原数据,而是“咔嚓”拍张照留底,生成个新版本。别人读的时候,按自己的“快照滤镜”挑能看的版本。读写互不干扰,堪称数据库界的时间管理大师⏰。

🆔 每行数据的“隐形身份证”

你以为表里只有你写的字段?Too young!InnoDB 每行数据屁股后面,其实还藏着 3 个你看不见但天天在加班的隐藏列:

隐藏字段 大小 人话解释
DB_TRX_ID 6 字节 最后谁动过这行?把事务 ID 亮出来!
DB_ROLL_PTR 7 字节 回滚指针,像根绳子 🪢,牵着 Undo Log 里的旧版本。
DB_ROW_ID 6 字节 没主键时系统自动发的“临时工编号”。

你看不见它们,但它们默默扛下了所有 🎒。


🪆 版本链:数据的“前任文学”

每次你 UPDATE 一下,好戏就开场了:

  1. 把旧数据打包扔进 Undo Log(存档)。
  2. 新数据的 DB_ROLL_PTR 拴住这条旧记录。
  3. 再来一次 UPDATE,再拴一条……

久而久之,一条从新到旧的版本链就串起来了。就像俄罗斯套娃 🪆,剥开最新的一层,里面全是历史版本。MySQL 就靠这条链子,随时能“穿越”回去找旧数据。


👁️ Read View:快照的“取景框”

事务执行普通 SELECT(快照读)时,MySQL 会“咔嚓”生成一个 Read View。这玩意儿就是决定你能看到啥的 VIP 通行证🎫。

它肚子里装了啥核心信息?

  • min_trx_id:当前活着的、最小事务 ID。
  • max_trx_id:系统下一个要发的事务 ID。
  • creator_trx_id:我自己(创建者)的 ID。
  • m_ids:当前还在摸鱼(未提交)的事务 ID 列表。

🧐 可见性判断:MySQL 的“查户口”四连问

拿到一个版本,先看它的 DB_TRX_ID,然后灵魂拷问:

  1. 是自己改的吗? (trx_id == creator_trx_id) → 废话,自己生的娃当然能看!✅
  2. 在我拍快照之前就搞定了吗? (trx_id < min_trx_id) → 老前辈了,早提交了,看!✅
  3. 在我拍快照之后才冒出来的? (trx_id >= max_trx_id) → 还没影儿的事,滚!❌ → 顺着 ROLL_PTR 找上一个版本。
  4. 还在活跃列表里摸鱼吗? (trx_id ∈ m_ids) → 没提交就想骗我?不看!❌ → 继续往前翻。

如果以上都不中?说明“大哥已经提交并溜了” → 看!✅
就按这个逻辑,顺着版本链从新到旧翻,找到第一个能看的,就是它了!🎯


🔍 RC vs RR:直播 vs 录播

MySQL 默认是 RR(可重复读),但很多互联网大厂偏爱 RC(读已提交)。差在哪?全在 Read View 的生成时机上 ⏱️:

隔离级别 快照怎么拍 体验 适用场景
🟢 RC 每次 SELECT 都现拍一张 像看直播📡,别人一提交,你立马刷到最新数据 库存秒杀、高并发写入,要的就是快 ⚡
🔴 RR 第一次 SELECT 拍完,后面全复用 像看录播📼,管别人怎么改,我这集剧情锁死不变 金融对账、报表统计,容不得半点变数 👑

👻 幻读:MVCC 能包治百病吗?

别闹,它只管“快照读”(普通 SELECT)。
如果你搞 SELECT ... FOR UPDATEUPDATEDELETEINSERT,这叫当前读。这时候 MVCC 直接下班 🚶‍♂️,全靠 Next-Key Lock(记录锁+间隙锁) 硬刚,把插队的路堵死,这才算真正防住幻读。

💡 辟谣时间:谁说“MVCC 解决了幻读”的?快照读靠 MVCC,当前读靠锁!别瞎传,容易挨打 🥊。


🗑️ Undo Log 膨胀:长事务的“作死现场”

MySQL 后台有个清洁工叫 Purge线程,专门负责清理没用的旧版本。
清理条件很苛刻:旧版本对应的事务必须已提交,且它的 ID 得小于所有活跃事务的 min_trx_id(也就是所有 Read View 都“看不上”它了)。

⚠️ 长事务的危害三连
你开个事务挂着不提交 → Purge 线程干瞪眼没法清理 → Undo 表空间像吹气球一样膨胀 🎈 → 版本链越来越长,查个数据得翻遍历史 → 从库同步也卡壳。

🔍 怎么抓长事务? 跑这句 SQL:

SELECT trx_id, trx_state, trx_started, trx_query
FROM information_schema.innodb_trx
WHERE trx_started < NOW() - INTERVAL 1 HOUR;

抓到直接 KILL,等清洁工收拾残局就行。MySQL 8.0 以后还加了自动截断 Undo、无锁化 Read View 等骚操作,高并发下快照读性能直接起飞 ✈️。


🛠️ 老司机调参 & 避坑指南

参数 默认值 老司机建议
transaction_isolation RR 90%互联网业务切 RC,并发更丝滑 🏎️
innodb_max_undo_log_size 1GB 业务猛就调到 2-4GB,给 Undo 留足卧室 🛏️
innodb_undo_tablespaces 2 高并发搞到 ≥4,分散 IO 压力 💨
innodb_purge_threads 4 写入密集调到 8-16,清洁工多招几个 🧹

🎯 最后说点掏心窝子的话
MVCC 的本质就一句:用 Undo 存历史,用 Read View 控制谁能看,实现读写“异地恋”
怎么选隔离级别?

  • 搞互联网业务?RC 走起,快就完了 ⚡
  • 搞金融对账?RR 稳住,一致性是爹 👑

写事务的三大铁律

  1. 短平快:控制在 3 秒内,别搞马拉松 🏃‍♂️
  2. 别拖家带口:事务里别调外部 RPC、别写慢查询
  3. 主动交代:显式 COMMIT,别指望自动提交能救你

🔍 排查套路:性能拉胯 → 查长事务 → 盯 Undo 空间 → 看 Purge 进度 → 缩小事务粒度。


🙋 互动环节
你被 Undo 表空间暴涨坑过没?是长事务赖着不走,还是 Purge 线程摸鱼?从库延迟是不是也背过这锅?
👇 评论区吐吐槽,咱们互相避雷~
觉得有用?点赞 👍+ 收藏 ❤️,就是对我最大的投喂!

📌 注:技术细节基于 MySQL 5.7.40 / 8.0.35 实测,生产环境请以实际版本+压测为准。延伸阅读:《MySQL 技术内幕:InnoDB 存储引擎》、官方文档、Percona 博客。

Logo

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

更多推荐