mysq mvcc实现细节

mysq mvcc实现细节
结合前文讨论的 ‌MySQL 事务隔离级别‌、‌锁定/非锁定读机制‌及‌并发场景差异‌MVCC多版本并发控制是实现“非锁定读”的核心底层技术。它通过在每行记录上保存多个版本使得不同事务能读取到符合其隔离级别要求的历史快照从而避免加锁带来的性能损耗。一、MVCC 的核心数据结构InnoDB 引擎通过以下三个隐藏列和两个系统列表来维护数据版本‌隐藏列每行数据自带‌DB_TRX_ID最近修改该行数据的事务 ID。DB_ROLL_PTR回滚指针指向该行数据在 Undo Log 中的上一个版本。DB_ROW_ID隐含的行 ID若表无主键则用于生成聚簇索引。‌Undo Log回滚日志‌存储数据的历史版本。当数据被修改时旧版本会被写入 Undo Log并通过 DB_ROLL_PTR 形成一条‌版本链‌。‌Read View读视图‌事务在启动或首次执行查询时生成的“快照”决定了该事务能看到哪个版本的数据。二、版本链的形成过程假设一行数据被多个事务依次修改其在 Undo Log 中会形成如下链表结构‌当前最新版本‌由最新事务修改DB_TRX_ID 为最新事务 ID。‌上一版本‌通过 DB_ROLL_PTR 指向 Undo Log 中的旧记录。‌更早版本‌继续通过回滚指针向前追溯直到初始版本。text[当前版本 (TRX103)] -- [版本2 (TRX102)] -- [版本1 (TRX101)] -- [初始版本]三、Read View 的可见性判断规则当事务执行普通 SELECT非锁定读时会将当前行的 DB_TRX_ID 与自身 Read View 中的信息进行对比。Read View 包含四个关键属性m_ids生成 Read View 时系统中活跃未提交的事务 ID 列表。min_trx_idm_ids 中最小的事务 ID。max_trx_id生成 Read View 时系统应分配给下一个事务的 ID。creator_trx_id创建该 Read View 的事务 ID。可见性算法逻辑若 DB_TRX_ID min_trx_id说明修改该行的事务在生成快照前已提交‌可见‌。若 DB_TRX_ID max_trx_id说明修改该行的事务在生成快照后才启动‌不可见‌。若 min_trx_id DB_TRX_ID max_trx_id若 DB_TRX_ID 在 m_ids 列表中说明修改事务在快照生成时仍活跃未提交‌不可见‌需沿版本链找下一个版本。若 DB_TRX_ID 不在 m_ids 列表中说明修改事务在快照生成前已提交‌可见‌。四、不同隔离级别下的 Read View 生成时机MySQL MVCC 实现细节‌特别是 Read View 生成机制与版本链MVCC 在 ‌读已提交RC‌ 和 ‌可重复读RR‌ 隔离级别下的核心差异主要体现在 ‌Read View读视图的生成时机‌ 不同从而导致了可见性行为的差异。1、核心差异Read View 生成时机特性读已提交 (RC)可重复读 (RR)‌生成时机‌‌每次‌执行普通 SELECT 时都会重新生成一个新的 Read View。仅在事务中‌第一次‌执行普通 SELECT 时生成后续查询复用该 Read View。‌快照一致性‌语句级快照每条语句看到的数据可能不同。事务级快照整个事务期间看到的数据始终一致。‌并发问题‌解决了脏读但存在‌不可重复读‌。解决了脏读和‌不可重复读‌。2、场景化差异演示假设事务 A 正在执行事务 B 在中间修改并提交了数据。RC 级别下的表现不可重复读‌T1‌事务 A 执行 SELECT生成 Read View 1读到值 ‌V1‌。‌T2‌事务 B 修改值为 ‌V2‌ 并 ‌COMMIT‌。‌T3‌事务 A 再次执行 SELECT‌重新生成‌ Read View 2。由于 B 已提交Read View 2 认为 B 的事务 ID 不在活跃列表中因此‌能看到 V2‌。‌结果‌同一事务内两次读取结果不一致V1 - V2。RR 级别下的表现可重复读‌T1‌事务 A 执行 SELECT生成 Read View 1读到值 ‌V1‌。‌T2‌事务 B 修改值为 ‌V2‌ 并 ‌COMMIT‌。‌T3‌事务 A 再次执行 SELECT‌复用‌ Read View 1。Read View 1 中记录 B 的事务 ID 为“活跃”或“未来”根据可见性算法A ‌依然看不到 V2‌只能沿版本链找到 V1。‌结果‌同一事务内两次读取结果一致V1 - V1。3、底层逻辑解析‌RC 为什么能读到最新提交‌因为每次查询都生成新的 Read Viewm_ids活跃事务列表会实时更新。之前未提交的事务现在提交了就不在 m_ids 中了因此对新 Read View 而言是“可见”的。‌RR 为什么能保持快照一致‌因为 Read View 在事务首次查询时固化。即使其他事务提交了新数据其事务 ID 依然存在于初始 Read View 的 m_ids 或大于 max_trx_id导致当前事务始终判定这些新版本“不可见”从而强制读取旧版本快照。4、总结与建议‌RC 适用场景‌对数据实时性要求高允许在长事务中读到其他事务刚提交的新数据如统计报表、即时状态查询。‌RR 适用场景‌需要保证事务内部逻辑一致性避免因为数据中途变化导致业务逻辑错误如银行转账、复杂订单处理。‌MySQL 默认选择 RR‌正是为了在大多数业务场景中提供更强的确定性。隔离级别Read View 生成时机效果描述‌读已提交 (RC)‌‌每次执行 SELECT 时‌重新生成总是能读到最新已提交的数据解决了脏读但出现不可重复读。‌可重复读 (RR)‌‌第一次执行 SELECT 时‌生成后续复用整个事务期间看到的数据快照一致解决了不可重复读是 MySQL 默认行为。五、MVCC 与锁定读的关联‌非锁定读‌完全依赖 MVCC 机制通过 Read View 和版本链获取历史数据不加锁。‌锁定读‌FOR UPDATE/SHARE‌不使用 MVCC‌而是直接读取最新已提交数据并加锁。因为锁定读的目的是为了后续修改必须确保拿到的是最新值且要阻塞其他事务所以绕过了快照读取逻辑。六、总结MVCC 的本质是‌“用空间换时间”‌通过 Undo Log 存储多个版本空间换取了读写操作的高并发性能。它使得读操作不需要等待写操作释放锁极大地提升了 MySQL 在处理高并发查询时的吞吐量。