数据库界的“平行宇宙”:MySQL 怎么用 MVCC 让读写不掐架?
🎬 数据库界的“平行宇宙”:MySQL 怎么用 MVCC 让读写不掐架?
同事灵魂拷问:“为啥我事务里查不到别人刚提交的数据?”
我嘬了口冰美式:“因为 MySQL 偷偷给你开了个时光机啊。”
同事:“???数据库还能拍科幻片?”
今天咱就搬好小板凳,唠唠 InnoDB 是怎么靠 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 一下,好戏就开场了:
- 把旧数据打包扔进 Undo Log(存档)。
- 新数据的
DB_ROLL_PTR拴住这条旧记录。 - 再来一次 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,然后灵魂拷问:
- 是自己改的吗? (
trx_id == creator_trx_id) → 废话,自己生的娃当然能看!✅ - 在我拍快照之前就搞定了吗? (
trx_id < min_trx_id) → 老前辈了,早提交了,看!✅ - 在我拍快照之后才冒出来的? (
trx_id >= max_trx_id) → 还没影儿的事,滚!❌ → 顺着ROLL_PTR找上一个版本。 - 还在活跃列表里摸鱼吗? (
trx_id ∈ m_ids) → 没提交就想骗我?不看!❌ → 继续往前翻。
如果以上都不中?说明“大哥已经提交并溜了” → 看!✅
就按这个逻辑,顺着版本链从新到旧翻,找到第一个能看的,就是它了!🎯
🔍 RC vs RR:直播 vs 录播
MySQL 默认是 RR(可重复读),但很多互联网大厂偏爱 RC(读已提交)。差在哪?全在 Read View 的生成时机上 ⏱️:
| 隔离级别 | 快照怎么拍 | 体验 | 适用场景 |
|---|---|---|---|
| 🟢 RC | 每次 SELECT 都现拍一张 |
像看直播📡,别人一提交,你立马刷到最新数据 | 库存秒杀、高并发写入,要的就是快 ⚡ |
| 🔴 RR | 第一次 SELECT 拍完,后面全复用 |
像看录播📼,管别人怎么改,我这集剧情锁死不变 | 金融对账、报表统计,容不得半点变数 👑 |
👻 幻读:MVCC 能包治百病吗?
别闹,它只管“快照读”(普通 SELECT)。
如果你搞 SELECT ... FOR UPDATE、UPDATE、DELETE、INSERT,这叫当前读。这时候 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稳住,一致性是爹 👑
写事务的三大铁律:
- 短平快:控制在 3 秒内,别搞马拉松 🏃♂️
- 别拖家带口:事务里别调外部 RPC、别写慢查询
- 主动交代:显式
COMMIT,别指望自动提交能救你
🔍 排查套路:性能拉胯 → 查长事务 → 盯 Undo 空间 → 看 Purge 进度 → 缩小事务粒度。
🙋 互动环节
你被 Undo 表空间暴涨坑过没?是长事务赖着不走,还是 Purge 线程摸鱼?从库延迟是不是也背过这锅?
👇 评论区吐吐槽,咱们互相避雷~
觉得有用?点赞 👍+ 收藏 ❤️,就是对我最大的投喂!
📌 注:技术细节基于 MySQL 5.7.40 / 8.0.35 实测,生产环境请以实际版本+压测为准。延伸阅读:《MySQL 技术内幕:InnoDB 存储引擎》、官方文档、Percona 博客。
更多推荐



所有评论(0)