MVCC深入:Read View、版本链与快照读——InnoDB并发控制的内核

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

之前写过事务隔离级别------脏读、不可重复读、幻读。但那是表象。

隔离级别是怎么实现的?为什么InnoDB能做到"读不阻塞写、写不阻塞读"?为什么RR级别下幻读没有被完全解决?

答案都在MVCC(多版本并发控制)里。

今天把MVCC的底层机制彻底拆开讲清楚。

一、什么是MVCC

MVCC(Multi-Version Concurrency Control),多版本并发控制。它的核心思想是:同一行数据在数据库中保留多个版本,不同事务读取时看到的是符合自己隔离级别的那个版本。

InnoDB为每一行数据维护了三个隐藏字段:

  • DB_TRX_ID(6字节):最后一次修改该行的事务ID

  • DB_ROLL_PTR(7字节):指向Undo Log中该行的上一个版本

  • DB_ROW_ID(6字节):如果没有显式主键,InnoDB用它生成聚簇索引

DB_ROLL_PTR是版本链的关键------通过它,当前行可以一路回溯到所有历史版本。

二、版本链是怎么构建的

每次对数据进行修改(UPDATE或DELETE),InnoDB都会在Undo Log中记录修改前的旧值。修改后,当前行的DB_ROLL_PTR指向Undo Log中的旧版本。

举个例子:一个事务把id=1的name从"张三"改成"李四",再把age从25改成26。版本链会变成:

bash 复制代码
当前行: name=李四, age=26, trx_id=100, roll_ptr -> Undo Log
   ↑
Undo Log: name=李四, age=25, trx_id=100, roll_ptr -> Undo Log
   ↑
Undo Log: name=张三, age=25, trx_id=90, roll_ptr -> NULL

版本链的每一个版本都记录了修改它的事务ID。 这个ID是Read View判断"哪个版本可见"的核心依据。

三、Read View:决定哪个版本可见

Read View是事务在快照读时生成的一个"可见性判断规则"。它包含四个核心字段:

字段 含义
m_ids 当前活跃事务ID列表(已启动但未提交的事务)
min_trx_id 活跃事务中最小的事务ID
max_trx_id 下一个将要分配的事务ID
creator_trx_id 创建这个Read View的事务ID

当一个事务要读取某行数据时,沿着版本链从头开始找:

  1. 如果版本的trx_id小于min_trx_id,说明这个版本在Read View创建之前就已提交------可见

  2. 如果版本的trx_id大于等于max_trx_id,说明这个版本在Read View创建之后才启动------不可见

  3. 如果trx_id在min_trx_id和max_trx_id之间:如果trx_id在m_ids列表中,说明事务还活跃------不可见 ;如果不在,说明已提交------可见

沿着版本链一直找到第一个可见的版本,就是当前事务能读到的数据。

四、RC和RR的Read View差异

这是MVCC最核心的差异点。

隔离级别 Read View生成时机 效果
READ COMMITTED 每次SELECT都生成新的Read View 能读到其他事务最新提交的数据
REPEATABLE READ 只在事务第一次SELECT时生成,之后复用 整个事务期间看到的是同一份快照

RC下,事务A第一次SELECT时看到的是当时的数据快照。事务B提交后,事务A再次SELECT,会生成新的Read View,看到事务B提交的数据。这就是"读已提交"。

RR下,事务A第一次SELECT生成Read View后,后续所有SELECT都复用这个Read View。即使事务B提交了,事务A也看不到------因为它仍然用旧Read View判断可见性。这就是"可重复读"。

这也解释了为什么RR级别下幻读没有被完全解决。 快照读(普通SELECT)下,RR确实避免了幻读------因为Read View不变,新插入的行对当前事务不可见。但当前读 (SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE)会读取最新数据,如果另一个事务插入了新行,当前读会看到它------这就是幻读。

五、快照读与当前读

InnoDB的读操作分两种:

快照读(Snapshot Read) :普通的SELECT语句。读取的是Read View决定的历史版本,不加锁,不阻塞写。

当前读(Current Read) :SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、INSERT、UPDATE、DELETE。读取的是最新版本,需要加锁。

读类型 语句 是否加锁 读取版本
快照读 普通SELECT 不加锁 Read View决定的版本
当前读 FOR UPDATE / 写操作 加锁 最新版本

MVCC的核心价值就在于快照读------读操作不需要加锁,写操作不会被读阻塞。这是InnoDB高并发性能的基石。

六、长事务与Undo Log膨胀

MVCC有一个代价:历史版本必须保留到不再被任何Read View需要为止。

如果一个事务长时间不提交,它持有的Read View会一直阻止InnoDB清理旧版本。Undo Log不断膨胀,占用大量表空间。

监控长事务:

bash 复制代码
SELECT trx_id, trx_state, trx_started, 
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec
FROM information_schema.innodb_trx
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;

如果发现长事务,需要评估是否可以提交或回滚。监控指标Innodb_history_list_length持续增长,说明Undo Log清理不及时,版本链过长。

七、小结

MVCC是InnoDB并发控制的内核。版本链记录历史,Read View决定可见性,快照读利用MVCC实现无锁读,当前读读取最新版本并加锁。RC和RR的核心差异在于Read View的生成时机------每次SELECT vs 首次SELECT。理解MVCC,才能理解InnoDB为什么能做到高并发下的读写不互相阻塞。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~

相关推荐
倔强的石头1067 分钟前
MoE专家混合架构深度解析
人工智能·架构
DLYSB_13 分钟前
门店断网了告警还在吗:边缘站点的缓存与续传架构
缓存·架构·报警灯
行者全栈架构师14 分钟前
【鸿蒙心迹】从手机到平板、折叠屏——多端适配自查清单与踩坑实录(HarmonyOS 7.x)
前端·算法·架构
Java后端的Ai之路15 分钟前
07React_Props验证器
人工智能·python·react.js·架构·fastapi
星云API技术笔记19 分钟前
企业微信外部群机器人实战:多人同时咨询时,如何准确匹配消息与回复对象
数据库
ClouGence20 分钟前
开源数据库管理工具 CloudDM 4.4.0 发布,新增 Kafka、SAP HANA 数据源
数据库·kafka
瀚高PG实验室33 分钟前
业务无法通过hgproxy访问数据库
数据库·postgresql·瀚高数据库
鱼很腾apoc40 分钟前
【MySQL】 第1期 从核心概念到表约束全解析
linux·c语言·数据库·学习·mysql
10mAh1 小时前
【SQL】NOT IN 遇到 NULL 为什么返回空集?三值逻辑与反关联实战
jvm·数据库·sql
Imagination官方博客1 小时前
PowerVR 上的 LLM 性能与加速
人工智能·架构·llama·gpu算力