接下来深入探讨底层机制MVCC多版本并发控制。一、数据库面临的三种场景读 - 读两个人同时看一篇文章。没有冲突完全不管。写 - 写两个人同时修改同一个账户余额。必须加排他锁必须排队。读 - 写一个人在修改数据另一个人正在查数据。读未提交 / 串行化 只要有人写读的人就得等。读提交 / 可重复读不加锁写的人在当前版本写读的人去读历史版本。这种无锁的并发控制技术就叫做MVCC。二、MVCC 是怎么保存历史版本的既然读的人要去读“历史版本”那历史版本存在哪呢靠两样东西。数据的隐藏字段MySQL 每一行数据其实不仅有你建表的那些列还有几个隐藏列重点记两个DB_TRX_ID事务ID记录了“最后一次修改这行数据的事务”是几号。DB_ROLL_PTR回滚指针指向这行数据的上一个版本。Undo Log回滚日志每当你要修改一行数据比如事务10把“张三”改成“李四”MySQL 不会直接抹掉张三而是先把“张三”这条老数据拷贝一份放到 Undo Log里然后再把表里的名字改成“李四”。此时“李四”的回滚指针就指向了 Undo Log 里的“张三”。后来事务11又把“李四”的年龄改成38那么会把28的老数据拷贝在undo log回滚指针指向老数据的地址。结果形成了一条长长的“历史版本链条”。三、Read View链条有了此时你开启了一个事务来读取这行数据。既然普通的 SELECT 是不加锁的快照读那你到底该读到李四还是张三呢这就需要用到 Read View读视图。你可以把 Read View 理解为你在执行SELECT 那一瞬间用相机咔嚓拍下的一张“系统活跃事务快照”记录了此时此刻系统里有哪些事务正在运行还没 commit。实际会记录m_ids;//一张列表用来维护Read View生成时刻系统正活跃的事务IDup_limit_id;//记录m_ids列表中事务ID最小的ID(没有写错)low_limit_id;//ReadView生成时刻系统尚未分配的下一个事务ID也就是目前已出现过的事务ID的最大值1(也没有写错)creator_trx_id//创建该ReadView的事务ID拿着这张“照片”MySQL 会顺着版本链拿版本的 DB_TRX_ID是谁改的去套用一组规则规则 1如果这个数据是我自己改的可见trx_id creator_trx_id规则 2如果改这个数据的事务在拍照片之前早就提交了可见trx_id up_limit_id规则 3如果改这个数据的事务是在拍照片之后才突然冒出来的不可见去找上一个版本。trx_id low_limit_id规则 4如果改这个数据的事务在拍照片时正好处于照片里的“未提交活跃列表”中说明它还没完事不可见去找上一个版本。trx_id ∈ m_ids就这样顺藤摸瓜直到找到第一个可见的历史版本为止。四、RC 和 RR 的本质区别RC读提交和 RR可重复读它们在底层全都使用了上述的 MVCC 和 Read View 机制普通的读取全都不加锁。那它们的区别到底在哪仅仅在于“拍照生成 Read View的时机不同”在 RC读提交级别下在这个事务里每一次执行 SELECT都会重新拍一张最新的照片生成全新的 Read View。既然每次都拍新照片如果别人刚改完数据并 commit 了你拍新照片时他就不在“活跃列表”里了你就能看到他的修改。这就叫读到了别人已提交的数据RC但也导致了“不可重复读”前后两次查的数据不一样。在 RR可重复读级别下你在这个事务里只有第一次执行 SELECT 时会拍一张照片生成 Read View。以后不管你执行多少次查询都对着这张旧照片去比对。哪怕别人提交了无数次修改只要在你的旧照片里那些人是“未提交”或者“还没出生”的统统看不见。每次查出来的数据永远和第一次一模一样。这就完美实现了“可重复读”。