既然你之前提到了“读提交时读取快照”以及“可重复读”的区别,咱们就深挖一下 MVCC(Multi-Version Concurrency Control,多版本并发控制) 的底层逻辑。
作为资深开发,理解 MVCC 不能只停留在概念,要盯着这三个核心:隐式字段、Undo Log 版本链 和 ReadView(一致性视图)。
1. 核心原理:它是如何实现“不加锁”的?
在没有 MVCC 之前,为了保证数据一致性,读写是互斥的。MVCC 的核心思想是:写操作更新数据的同时,为旧数据保留一个快照。 这样,读操作就可以去读旧版本,写操作去写新版本。
2. 三个关键的“隐式字段”
在 InnoDB 中,每一行数据在聚簇索引中除了你定义的列,还偷偷藏了三个字段:
-
DB_TRX_ID(6字节):最近一次修改(插入或更新)该行的事务 ID。 -
DB_ROLL_PTR(7字节):回滚指针。它指向该行在Undo Log里的上一个版本。 -
DB_ROW_ID(6字节):隐藏的主键(如果你没定义主键,InnoDB 就会用它)。
3. Undo Log 版本链:数据的“平行时空”
当你修改一行数据时,InnoDB 不会直接覆盖旧数据,而是:
-
把当前数据复制到
Undo Log中。 -
修改当前行的数据,并更新
DB_TRX_ID为当前事务 ID。 -
让
DB_ROLL_PTR指向刚才拷贝到Undo Log里的旧版本。
随着多个事务的修改,就形成了一个版本链。链头是最新的,链尾是最旧的。
4. ReadView:谁能看哪个版本?
这是 MVCC 最精妙的部分。当你执行 SELECT 时,InnoDB 会生成一个 ReadView,里面记录了四个关键信息:
-
m_ids:当前系统中活跃(还没提交)的事务 ID 列表。 -
min_trx_id:活跃事务中最小的 ID。 -
max_trx_id:系统即将分配给下一个事务的 ID 值。 -
creator_trx_id:生成这个 ReadView 的事务 ID。
可见性算法逻辑:
当事务读取某行数据时,会拿该行的 DB_TRX_ID 去跟 ReadView 比对:
-
小于
min_trx_id:说明这个版本在快照生成前就提交了,可见。 -
大于等于
max_trx_id:说明这个版本是在快照生成后才开启的,不可见。 -
在两者之间:
-
如果在
m_ids列表里:说明快照生成时这个事务还没提交,不可见。 -
如果不在
m_ids列表里:说明快照生成前它已经提交了,可见。
-
5. RC vs RR:ReadView 生成的时机不同
这解释了你之前的疑问:
-
RC (Read Committed):每次
SELECT都会生成一个新的 ReadView。所以别人提交了,你下一秒再查,ReadView 变了,就能看到最新数据。 -
RR (Repeatable Read):只有第一次
SELECT时生成 ReadView,后续整个事务都用这一个。所以不管别人怎么改,你看到的永远是事务开始那一刻的样子。
🛠 资深开发的思考
-
为什么会有长事务风险?:如果一个事务一直不提交,MVCC 就必须保留所有版本链(Undo Log)。这会导致 Undo Log 膨胀,撑爆磁盘空间,并拖慢查询速度(因为要顺着链表找很久)。
-
快照读 vs 当前读:
-
普通的
SELECT是快照读,走 MVCC。 -
UPDATE/DELETE/INSERT和SELECT ... FOR UPDATE是当前读,必须加锁读最新的。
-
📉 状态同步
-
2026-03-29:已完成 MVCC 底层机制(隐式字段、Undo Log 链、ReadView 算法)的深度解析。
理解了 MVCC,你就明白了为什么 MySQL 默认级别下能兼顾高性能和数据一致性。
既然提到了 Undo Log 膨胀,你想了解一下如何通过监控手段(比如 information_schema)来定位那些长期占用版本链的“僵尸事务”吗?