进阶-InnoDB引擎-MVCC
2026/6/8 13:40:49 网站建设 项目流程

一、MySQL进阶

“当两个用户同时查看同一份数据,你希望看到的是‘当前状态’,还是‘事务开始时的状态’?”
—— MVCC(多版本并发控制),让数据库在高并发下依然保持“一致性”与“高效性”

为什么需要MVCC?——一场“读写冲突”的危机

在传统数据库中,读操作会阻塞写操作,写操作会阻塞读操作。例如:

  • 事务A:UPDATE orders SET status='shipped' WHERE order_id=100
  • 事务B:SELECT * FROM orders WHERE order_id=100

如果事务B在事务A未提交时执行,会发生什么?

  • 悲观锁:事务B必须等待事务A提交(锁表),导致性能下降
  • 乐观锁:事务B直接读取,但可能读到未提交数据(脏读)

💡2010年,某电商网站因锁表问题导致双11期间响应时间从50ms飙升至500ms,损失超百万!
这就是MVCC诞生的背景在不加锁的情况下,实现高并发读写

1. InnoDB引擎-MVCC

📌 什么是MVCC?

MVCC(Multi-Version Concurrency Control)是一种并发控制机制,它通过保存数据的多个版本,让读操作无需等待写操作,从而实现“读不阻塞写,写不阻塞读”

📌 两种读操作:

类型说明举例是否加锁
当前读(Current Read)读取最新数据(已提交)SELECT ... FOR UPDATE
UPDATE
DELETE
✅ 加锁(X锁)
快照读(Snapshot Read)读取事务开始时的快照SELECT *❌ 不加锁

💡关键点快照读是MVCC的核心,它利用历史版本实现非阻塞读。

MVCC的核心组件:三个隐式字段

InnoDB 通过三个隐藏字段(在表中不可见)实现MVCC

字段作用类型说明
DB_TRX_ID记录最近修改该行的事务ID6字节用于判断行是否对当前事务可见
DB_ROLL_PTR指向Undo Log的指针7字节用于找到历史版本
DB_ROW_ID隐藏的行ID(自增)6字节在索引中作为回表的唯一标

💡为什么需要这些字段?

  • DB_TRX_ID:判断行是否已提交
  • DB_ROLL_PTR:通过Undo Log找到旧版本
  • DB_ROW_ID:在索引中唯一标识行(即使主键未指定)

Undo Log:数据的“时间胶囊”

Undo Log 是 MVCC 的基础,它记录了数据修改前的旧值,用于:

  • 事务回滚
  • MVCC 快照读

📌 Undo Log 两种类型:

类型作用说明
INSERT记录插入的行用于事务回滚(删除时需恢复)
UPDATE记录修改前的旧值用于快照读和回滚(更新时需恢复)

💡Undo Log 的存储
由 InnoDB 的Undo Tablespace管理,可以配置多个 Undo Log 文件(MySQL 5.7+ 支持独立Undo表空间)。

数据版本链:Undo Log 的“链式存储”

当一行数据被多次修改时,Undo Log 会形成版本链

最新版本 → (DB_ROLL_PTR) → 旧版本 → (DB_ROLL_PTR) → 更旧版本 → ... → 初始版本

💡举例

  • 初始:balance=1000
  • 事务1:UPDATE balance=800→ 生成Undo Log(记录1000)
  • 事务2:UPDATE balance=500→ 生成Undo Log(记录800)

版本链:500 → 800 → 1000

ReadView:MVCC的“时间机器”

ReadView是事务开始时生成的快照,它决定了哪些版本对当前事务可见。

📌 ReadView 包含的关键信息:

字段说明
min_trx_id活跃事务最小ID(当前正在执行的事务ID)
max_trx_id活跃事务最大ID(当前正在执行的事务ID上限)
m_ids活跃事务ID列表(当前正在执行的事务ID集合)
def is_visible(trx_id, readview): if trx_id < readview.min_trx_id: # 已提交 return True elif trx_id >= readview.max_trx_id: # 未提交 return False else: # 在活跃事务列表中 if trx_id in readview.m_ids: # 未提交 return False else: # 已提交 return True

💡关键点

  • RR级别(可重复读):事务开始时生成ReadView,后续查询都使用同一ReadView
  • RC级别(读已提交):每次查询都生成新的ReadView

MVCC工作原理:一次事务的“时间穿越”

假设执行:

-- 事务A(ID=100) START TRANSACTION; SELECT * FROM accounts WHERE user_id=1; -- 快照读 UPDATE accounts SET balance=500 WHERE user_id=1; -- 当前读 COMMIT; -- 事务B(ID=101) START TRANSACTION; SELECT * FROM accounts WHERE user_id=1; -- 快照读

📌 MVCC如何工作?

  1. 事务A开始
    • 生成ReadView:min_trx_id=100, max_trx_id=102, m_ids=[100]
    • 执行SELECT:快照读,使用ReadView判断可见性
  2. 事务A执行UPDATE
    • 修改行,生成Undo Log(记录旧值:1000 → 500)
    • DB_TRX_ID更新为100
    • DB_ROLL_PTR指向Undo Log
  3. 事务B执行SELECT
    • 生成ReadView:min_trx_id=101, max_trx_id=102, m_ids=[101]
    • 读取行:DB_TRX_ID=100 < 101→ 可见(值为1000)

🌟效果:事务B在事务A未提交时,读到的是1000(事务A开始时的值),而非500(当前值)。

MVCC在不同隔离级别下的表现

隔离级别快照读可见性规则幻读说明
读未提交不使用MVCC直接读最新数据无MVCC
读已提交使用MVCC每次查询生成新ReadView无法避免不可重复读
可重复读使用MVCC事务开始时生成ReadView通过Next-Key Lock避免幻读
串行化不使用MVCC强制加锁无MVCC

💡为什么RR级别能避免幻读?
InnoDB 在RR级别下使用Next-Key Lock(记录锁 + 间隙锁),在读取时不仅锁住记录,还锁住间隙,防止其他事务插入新数据。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询