尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

5.5 MVCC原理

5.5 MVCC原理 MySQL MVCC多版本并发控制原理完全指南MVCC 是 InnoDB 存储引擎最核心的技术之一也是 MySQL 能够在高并发场景下保持卓越性能的“秘密武器”。如果说锁是解决并发问题的“ brute force”暴力手段那 MVCC 就是“优雅的妥协”——让读写互不阻塞在保证一致性的前提下最大化并发能力。一、什么是 MVCCMVCCMulti-Version Concurrency Control多版本并发控制是一种不用加锁就能实现事务隔离的并发控制机制。它的核心思想非常简洁写操作不直接覆盖旧数据而是生成一个新版本读操作根据事务的隔离级别和时间戳选择合适的版本来读。这样一来读操作永远不用等待写操作写操作也不用等待读操作读写互不阻塞。生活类比你在写一份文档每隔一段时间保存一个历史版本。别人想读的时候可以选择读最新版也可以选择读某个历史版。你写你的他读他的互不干扰。重要前提InnoDB 的 MVCC仅在READ COMMITTEDRC和REPEATABLE READRR两个隔离级别下生效。READ UNCOMMITTED直接读最新版本SERIALIZABLE靠锁机制保证一致性都不使用 MVCC。二、MVCC 的三大核心组件MVCC 的实现依赖于三个紧密配合的组件隐藏字段 Undo Log 版本链 Read View。2.1 隐藏字段Hidden ColumnsInnoDB 为每一行数据悄悄添加了三个“看不见”的隐藏字段隐藏字段长度核心作用DB_TRX_ID6 字节最后修改该行的事务 ID全局递增DB_ROLL_PTR7 字节回滚指针指向 Undo Log 中该行的上一个版本DB_ROW_ID6 字节隐藏主键当表没有主键和唯一非空索引时自动生成关键认知DB_ROW_ID不是 MVCC 必需的只有当表没有显式主键时才会用到。纯只读事务只有 SELECT不会分配事务 ID其DB_TRX_ID默认为 0。2.2 Undo Log 版本链Version ChainUndo Log 是 MVCC 的“数据载体”。每一次对数据行的修改都会生成一条对应的 Undo Log通过DB_ROLL_PTR将所有历史版本串联成一条单向链表。版本链生成过程示例-- ① 插入初始数据事务ID3INSERTINTOuser(id,name,age)VALUES(1,张三,20);-- 此时DB_TRX_ID3DB_ROLL_PTRNULL版本链只有初始版本-- ② 第一次更新事务ID5UPDATEuserSETage21WHEREid1;-- 生成 undo logage20, DB_TRX_ID3-- 当前行age21, DB_TRX_ID5, DB_ROLL_PTR → undo log-- ③ 第二次更新事务ID8UPDATEuserSETage22WHEREid1;-- 生成新的 undo logage21, DB_TRX_ID5-- 当前行age22, DB_TRX_ID8, DB_ROLL_PTR → 新 undo log最终形成的版本链结构当前行 (age22, DB_TRX_ID8) ↑ DB_ROLL_PTR Undo Log V2 (age21, DB_TRX_ID5) ↑ DB_ROLL_PTR Undo Log V1 (age20, DB_TRX_ID3) ↑ DB_ROLL_PTR NULL初始版本2.3 Read View读视图/一致性视图如果说 Undo Log 版本链是 MVCC 的“数据载体”那么Read View 就是 MVCC 的“决策大脑”。它定义了当前事务能看到哪些数据版本、不能看到哪些数据版本。Read View 的四个核心字段字段含义m_ids生成 Read View 时当前所有活跃的未提交读写事务 ID 集合min_trx_idm_ids中的最小值最早的未提交事务max_trx_id生成 Read View 时系统下一个要分配的事务 ID非活跃事务最大值creator_trx_id生成当前 Read View 的事务自己的 ID只读事务为 0⚠️常见误区纠正max_trx_id不是活跃事务的最大值而是下一个要分配的事务 ID。比如当前活跃事务 ID 是 2、3、5那么max_trx_id 6而不是 5。这个认知错误会直接导致可见性判断完全错乱。三、可见性判断算法核心有了 Read View 和版本链InnoDB 会按照固定规则从版本链的最新版本开始依次判断每个版本是否可见直到找到第一个符合规则的版本。判断规则优先级从高到低对于版本链中某个版本其 DB_TRX_ID trx_id ① 如果 trx_id creator_trx_id → ✅ 可见当前事务自己修改的版本当然可见 ② 如果 trx_id min_trx_id → ✅ 可见该版本在生成 Read View 前已提交 ③ 如果 trx_id max_trx_id → ❌ 不可见该版本的事务在生成 Read View 后才启动 ④ 如果 min_trx_id trx_id max_trx_id → 判断 trx_id 是否在 m_ids 中 - 在 m_ids 中 → ❌ 不可见该事务仍未提交 - 不在 m_ids 中 → ✅ 可见该事务在生成 Read View 前已提交 ⑤ 如果当前版本不可见 → 沿着 DB_ROLL_PTR 找到上一个版本重复上述判断直观理解条件结论trx_id min_trx_id该事务早就提交了→ 可见trx_id in m_ids该事务还在运行→ 不可见trx_id max_trx_id该事务未来才启动→ 不可见trx_id creator_trx_id自己改的→ 可见四、RC 与 RR 的核心差异Read View 生成时机RC 和 RR 隔离级别的本质区别就在于Read View 的生成时机不同。对比项RC读已提交RR可重复读Read View 生成时机每次 SELECT都生成新的 Read View事务中第一次 SELECT时生成整个事务复用能否看到其他事务提交的修改✅ 能每次查询都看到最新提交❌ 不能只看到事务启动前的数据不可重复读❌ 存在✅ 已解决幻读快照读❌ 存在✅ 已解决InnoDB 特殊实现并发性能更高稍低4.1 RC 级别下的表现RC 级别下每次 SELECT 都生成新的 Read View因此只能看到已经提交的事务修改解决了脏读但因为每次 Read View 都是最新的会出现不可重复读。示例时间会话ARC会话BRCT1BEGINBEGINT2SELECT → age20T3UPDATE age21未提交T4SELECT → age20脏读解决T5COMMITT6SELECT → age21不可重复读T6 时会话A 生成了新的 Read View会话B 已提交所以看到了新数据。4.2 RR 级别下的表现RR 级别下事务内第一次 SELECT 生成 Read View 后整个事务复用因此看不到其他事务在事务启动后提交的修改解决了不可重复读。示例时间会话ARR会话BRRT1BEGINBEGINT2SELECT → age20生成 Read ViewT3UPDATE age21COMMITT4SELECT → age20复用 Read View看不到提交T3 时会话B 虽然提交了但会话A 复用第一次的 Read View根据可见性规则会话B 的trx_id在m_ids中因此修改不可见。五、快照读 vs 当前读MVCC 把 SELECT 操作分成了两种读类型对应 SQL读哪个版本是否加锁快照读Snapshot Read普通SELECT读 Read View 指向的快照版本历史数据❌ 不加锁当前读Current ReadSELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、UPDATE、DELETE、INSERT读行的最新版本✅ 加锁关键理解快照读不加锁只管读。RR 下读到事务快照建立时的数据版本RC 下读到当前语句执行那一刻的快照。当前读加锁读最新版。每次必须拿到最新的数据如果该行正被其他事务修改未提交会阻塞等待锁释放。-- 快照读不加锁读快照SELECT*FROMuserWHEREid1;-- 当前读加锁读最新SELECT*FROMuserWHEREid1FORUPDATE;SELECT*FROMuserWHEREid1LOCKINSHAREMODE;UPDATEuserSETnameBobWHEREid1;-- 也是当前读DELETEFROMuserWHEREid1;-- 也是当前读六、MVCC 与锁的协作MVCC 和锁不是替代关系而是协作关系冲突类型解决方案读-写冲突MVCC读写互不阻塞写-写冲突锁机制行锁、间隙锁、Next-Key Lock在 RR 级别下InnoDB 通过MVCC Next-Key Lock的组合彻底解决了幻读问题MVCC 层面整个事务复用同一个 Read View看不到其他事务新增的行锁层面Next-Key Lock记录锁 间隙锁阻止其他事务在查询范围内插入新数据七、MVCC 的局限性与注意事项局限性说明只能解决读写冲突写-写冲突仍然需要通过锁来解决Undo Log 占用空间大量历史版本占用磁盘空间需要 Purge 线程清理长事务是杀手长事务导致 Undo Log 无法被清理可能撑爆磁盘不支持 DDLDDL 操作加表级锁会阻塞所有读写操作生产环境黄金法则事务一定要短小精悍。长事务在 RR 级别下会长时间持有 Read View导致对应的 Undo Log 版本无法被 Purge 线程清理可能引发磁盘爆满和性能雪崩。八、总结核心要点关键描述本质通过维护数据行的多个历史版本实现读写互不阻塞的无锁并发控制三大组件隐藏字段DB_TRX_IDDB_ROLL_PTR Undo Log 版本链 Read View核心算法通过 Read View 的可见性规则从版本链中找到当前事务能看到的版本RC vs RRRC每次 SELECT 新建 Read ViewRR事务内复用第一次的 Read View快照读 vs 当前读快照读普通 SELECT不加锁读历史版本当前读FOR UPDATE/UPDATE加锁读最新版适用级别仅在 RC 和 RR 下生效一句话记住 MVCCMVCC 是 InnoDB 的“时光机”——通过 Undo Log 保存每个数据行的所有历史版本再通过 Read View 为每个事务拍一张“快照”让不同事务在同一时刻看到同一行数据的不同版本从而实现读写互不阻塞的高性能并发控制。面试高频考点MVCC 解决了什么问题——读写冲突让读不阻塞写、写不阻塞读RC 和 RR 在 MVCC 实现上有什么区别——Read View 生成时机不同快照读和当前读有什么区别——快照读不加锁读历史版本当前读加锁读最新版InnoDB 的 RR 级别如何解决幻读——MVCC复用 Read View Next-Key Lock
返回列表