MVCC机制
一、MVCC 到底是干嘛的MVCC 全称 Multi-Version Concurrency Control多版本并发控制核心作用就一个让多个人同时读写同一条数据不用互相锁着等读写不冲突、不阻塞大家都能顺畅操作。举个最直白的例子有人在修改一条数据写操作你同时去读这条数据读操作不用等他改完你能正常读到数据他也能正常修改互不干扰——这就是MVCC的功劳。二、核心比喻用“图书阅览室”理解MVCC全貌把MySQL数据库想象成一个「图书阅览室」帮你快速理解MVCC的所有核心组件「书」 数据库里的每一行数据「写操作」 有人要修改书里的内容比如把“1”改成“3”「读操作」 有人要翻看这本书「undo log」 阅览室的「档案柜」专门存放“书的旧版本”修改前先复印一份存档「Read View」 你进阅览室时管理员给你发的「阅读权限清单」规定你能看哪些版本的书「事务」 你在阅览室的「阅读/修改时长」从进来到离开期间你看到的书的版本是固定的。没有MVCC的情况有人改书就把阅览室锁起来别人不能进、不能看效率极低有MVCC的情况改书的人改新书看书的人读档案柜里的旧书互不打扰效率拉满。三、MVCC 核心原理3个关键组件MVCC能实现“读写不冲突”全靠3个核心组件协同工作结合比喻一步步讲透不跳任何步骤。1. 组件1每行数据的“隐藏3字段”InnoDB引擎会在每一行数据背后偷偷藏3个我们看不到的隐藏字段这是MVCC的基础相当于给每本书做了“版本标记”「DB_TRX_ID」哪个事务最后修改了这行数据相当于“这本书最后被谁修改过”有唯一的事务ID「DB_ROLL_PTR」版本指针指向undo log里的“旧版本数据”相当于“这本书的旧复印件存放在档案柜的哪个位置”「DB_ROW_ID」隐藏主键没有手动设置主键时MySQL会自动用这个字段当主键无关核心逻辑了解即可。2. 组件2undo logMVCC的“旧版本档案柜”之前讲三大日志时我们知道undo log是“回滚日志”但它还有一个更重要的作用——给MVCC提供“历史版本数据”相当于阅览室的档案柜专门存旧书复印件。undo log的写入时机执行增删改操作前先把修改前的旧数据写入undo log比如要把“1”改成“3”先把“1”抄一份存进档案柜再改书undo log的回收事务提交后不会立即删除而是“延迟回收”标记为“可回收”等没有事务再需要这份旧版本时后台自动清理避免有人还在看旧书时档案被删与MVCC的关联undo log里的每一条旧数据都是一行数据的“历史版本”通过DB_ROLL_PTR指针能串联起所有历史版本比如从“3”找到“1”再找到更早的版本。3. 组件3Read View当你开启一个事务第一次执行查询时MySQL会瞬间给你拍一张“快照”这张快照就是Read View相当于管理员给你的“阅读权限清单”——规定了你能看哪些版本的书不能看哪些。Read View里只有4个关键信息「m_ids」当前正在阅览室里、还没离开未提交的人事务列表「min_trx_id」这些未提交的人事务里ID最小的那个「max_trx_id」下一个要进入阅览室的人事务的ID相当于“下一个读者的编号”「creator_trx_id」你自己的事务ID相当于“你的读者编号”。核心作用Read View决定了你能看到哪一个版本的数据——不是最新的而是“你有权限看的、最适合的版本”。四、MVCC 核心逻辑怎么判断“我能读哪个版本”当你查询某一行数据时MySQL会拿着这行数据的「DB_TRX_ID」最后修改它的事务ID对照你的Read View阅读权限清单按4条简单规则判断全程不用复杂计算规则1自己改的能看如果这行数据的DB_TRX_ID最后修改者和你的creator_trx_id你自己的事务ID一样 → 这是你自己改的当然能看自己改的书自己肯定能看。规则2早于你进入的、已提交的能看如果这行数据的DB_TRX_ID比Read View里的min_trx_id当前未提交事务的最小ID还小 → 说明修改这行数据的人在你进入阅览室之前就已经离开提交事务了这是“历史稳定版本”能看别人早就改完走了你看他改后的版本没问题。规则3晚于你进入的、未提交的不能看如果这行数据的DB_TRX_ID大于或等于Read View里的max_trx_id下一个事务ID → 说明修改这行数据的人是在你之后进入阅览室的而且还没离开未提交你不能看别人还在改你不能看半成品。规则4和你同时在、未提交的不能看如果这行数据的DB_TRX_ID在min_trx_id和max_trx_id之间而且在m_ids当前未提交事务列表里 → 说明修改这行数据的人和你同时在阅览室未提交你不能看别人还没改完你看不到他的修改。关键补充读不到怎么办如果按上面的规则判断当前版本不能看MySQL会顺着这行数据的「DB_ROLL_PTR」指针去undo log档案柜里找“上一个旧版本”再用上面的规则重新判断直到找到一个“你能看的版本”为止。五、经典场景演示用“读旧数据、数据覆盖别人修改的数据”场景一步步演示MVCC的工作过程看完就懂为什么会读旧数据以及为什么会覆盖别人修改后的数据。场景初始数据 num1两个事务同时操作1.时刻1你开启事务Acreator_trx_id100执行查询SELECT num FROM t WHERE id1;MySQL给你生成Read Viewm_ids[], min_trx_id101, max_trx_id101, creator_trx_id100判断当前数据num1的DB_TRX_ID0初始无修改小于min_trx_id101能看你读到num1旧版本。2.时刻2别人开启事务Bcreator_trx_id101执行修改UPDATE t SET num3 WHERE id1先写undo log把num1的旧数据存入undo logDB_ROLL_PTR指向这条旧数据内存中把num改成3数据行的DB_TRX_ID更新为101事务B提交相当于“改书的人离开阅览室”。3.时刻3你在事务A中再次执行查询SELECT num FROM t WHERE id1;你依然用“时刻1生成的Read View”MySQL默认隔离级别下事务内Read View全程不变判断当前数据num3的DB_TRX_ID101等于max_trx_id101不能看顺着DB_ROLL_PTR去undo log找旧版本找到num1DB_TRX_ID0能看你读到的还是num1旧数据——这就是“读不到最新数据”的原因。4.时刻4你在事务A中执行修改UPDATE t SET num11 WHERE id1;你基于读到的旧数据1计算出2执行更新此时事务B已经提交数据实际是3但你看不到更新后数据被你改成2覆盖了别人的3——这就是“数据覆盖”的问题。六、核心疑问解答疑问1读不到最新数据岂不是数据有误结论不算错误这是MySQL“事务隔离”的正常行为是故意设计的。解释MVCC的核心目的是“保证同一个事务内看到的数据一致、不混乱”而不是“保证读到最新数据”。就像你手里拿着一份9点的报纸Read View不管之后出了多少晚报别人的修改你手里的报纸始终是9点的——宁可读旧数据也不能让你在同一个事务里前后读到不一样的数据比如第一次读1第二次读3算错账。补充如果需要读到最新数据不要用长事务读完就提交每次查询都会生成新的Read View就能看到别人提交的最新修改。疑问2重点读旧数据后修改会覆盖别人的新数据怎么避免结论MVCC只保证“读安全、读一致”不保证“写安全”单纯靠MVCC先读后改一定会出现覆盖需要额外加机制。3种小白也能上手的解决方案真实业务常用1.方案1SELECT ... FOR UPDATE加行锁最常用读数据时直接给数据上锁SELECT num FROM t WHERE id1 FOR UPDATE; 别人要改这条数据必须等你事务结束你读的一定是最新数据不会基于旧值修改避免覆盖。2.方案2原子更新SQL不用先读后改不要先读num1再计算2直接让MySQL自己计算UPDATE t SET num num 1 WHERE id1; 这是原子操作不会被打断MySQL会自动处理并发绝对安全。3.方案3乐观锁版本号机制给表加一个version字段版本号更新时判断版本是否一致UPDATE t SET num2, versionversion1 WHERE id1 AND version旧版本号; 如果别人改了数据版本号会变更新失败你重试即可。疑问3MVCC和undo log的关联到底是什么结论undo log是MVCC的“历史版本仓库”没有undo logMVCC就无法提供旧版本数据也就无法实现“读写不冲突”。简单说MVCC负责“判断你能看哪个版本”undo log负责“提供这个版本的数据”两者缺一不可——没有undo logMySQL找不到旧版本只能给你上锁就失去了MVCC的意义。七、MVCC 与事务隔离级别MVCC主要支持MySQL的2种事务隔离级别行为不同对应不同的业务场景结合Read View的生成时机就能看懂1. REPEATABLE READ可重复读MySQL默认核心特点事务开始时生成一次Read View全程用到底。效果不管别人怎么修改、怎么提交你在同一个事务里读到的始终是事务开始时的旧版本就像手里的报纸全程不变实现“可重复读”避免“不可重复读”但会一直读旧数据。2. READ COMMITTED读已提交核心特点每次执行SELECT时都会生成新的Read View。效果别人提交了新修改你下次查询就能看到最新数据不会一直读旧数据但可能出现“不可重复读”同一个事务里两次查询结果不一样。3. SERIALIZABLE串行化不使用MVCC直接给数据上锁读写排队执行完全避免并发问题但性能极低适合对数据一致性要求极高、并发量极低的场景如银行对账。八、极简总结1.MVCC 多版本并发控制核心是“读写不冲突、不阻塞”2.3个核心组件数据隐藏字段版本标记、undo log旧版本仓库、Read View阅读权限3.读旧数据不是错误是事务隔离的正常行为保证事务内数据一致4.MVCC不保证写安全先读后改会覆盖别人数据需用加锁、原子更新、乐观锁解决5.undo log不仅用于回滚更是MVCC的核心没有undo log就没有MVCC。