云计算百科
云计算领域专业知识百科平台

深入理解 MySQL:MVCC - 从版本链到事务隔离

往期博客

深入理解 MySQL 索引:从 B+ 树到索引下推 深入理解 MySQL 锁:从全局锁到死锁检测

在没有 MVCC 的远古时代,数据库为了保证数据一致性,只能依靠锁机制:读读不互斥,但读写、写写都会互斥。这意味着,如果一张 1.6 亿行的大表正在进行报表查询,所有的写入操作都会被阻塞——这在高并发系统中是不可接受的。

MVCC(多版本并发控制)的出现,就是为了实现"读写不互斥"。 核心思想很简单:你改你的,我读我的历史版本。

MVCC 的两大幕后功臣

要实现"读不阻塞写",MySQL 并不真的去复制整张表,而是巧妙地利用了两个机制:隐藏字段和 Undo Log。

隐藏字段:每行数据的身份牌

当你创建一张表时,InnoDB 会偷偷给每一行加上几个隐藏列:

字段作用
DB_TRX_ID 最近一次修改该行的事务 ID
DB_ROLL_PTR 回滚指针,指向该行的"前世"(存在 Undo Log 中)
DB_ROW_ID 隐藏主键(仅当表没有定义主键时存在)

Undo Log:数据的后悔药与时光机

每当你更新一行数据,InnoDB 的处理流程是:

  • 把旧值写进 Undo Log
  • 让当前记录的 DB_ROLL_PTR 指向这个 Undo Log
  • 这样,一连串的修改就形成了一条版本链。通过这条链,任何事务都可以追溯到它"应该看到"的那个历史版本。

    Read View:决定你能看到哪个版本

    有了版本链,接下来的关键问题是:我该看哪一个版本?这就是 Read View(一致性视图) 的职责。

    Read View 包含四个核心信息:

    字段含义
    m_ids 当前系统中所有活跃(未提交)的事务 ID 列表
    min_trx_id m_ids 中的最小值
    max_trx_id 系统准备分配给下一个事务的 ID
    creator_trx_id 创建该 Read View 的事务 ID

    可见性判断逻辑

    当一个事务尝试读取某行记录时,会拿着该记录的 DB_TRX_ID 按以下规则判断:

  • trx_id == creator_trx_id:自己改的,可见
  • trx_id < min_trx_id:在创建快照前就已提交,可见
  • trx_id >= max_trx_id:在创建快照后才开启的事务,不可见
  • min_trx_id <= trx_id < max_trx_id:
    • 在 m_ids 中 → 还没提交,不可见
    • 不在 m_ids 中 → 已提交,可见
  • 如果当前版本不可见,就顺着 DB_ROLL_PTR 往版本链上回溯,直到找到一个可见的版本。

    事务隔离级别

    并发带来的三大问题

    数据库并发处理就像多个人同时编辑一个文档,不加控制会出现三个经典问题:

    问题描述
    脏读 读到了其他事务未提交的数据,如果对方回滚,读到的就是"假数据"
    不可重复读 同一事务内两次读同一数据,结果不同(中间被别人改了)
    幻读 同一事务内两次查同一范围,发现多了或少了几行

    四大隔离级别

    SQL 标准定义了四个隔离级别,级别越高越安全,但并发性能越差:

    隔离级别脏读不可重复读幻读实现原理
    Read Uncommitted 裸奔,基本不用
    Read Committed (RC) MVCC:每条 SQL 生成 Read View
    Repeatable Read (RR) 部分解决 MVCC:事务启动时生成 Read View
    Serializable 全表加锁,完全串行

    RC vs RR:Read View 生成时机的差异

    这是 MVCC 最核心的知识点:

    RC(Read Committed):每条 SELECT 都会重新生成 Read View。如果事务 B 在中途提交了,事务 A 下一次查询就能看到 B 的修改——这就是"不可重复读"的根源。

    RR(Repeatable Read):只在第一条 SELECT 时生成 Read View,整个事务期间复用。即使事务 B 提交了,事务 A 的视野里它仍然是"未提交"状态——这就保证了可重复读。

    关于其他两个级别

    • Read Uncommitted:根本不使用 MVCC,直接读最新版本,会产生脏读
    • Serializable:将所有 SELECT 隐式转换为 SELECT … FOR SHARE,主要靠锁来保证安全

    面试高频问题

    1. MySQL 的默认隔离级别是什么?

    Repeatable Read (RR)。这与 Oracle、PostgreSQL 默认使用 RC 不同。MySQL 坚持用 RR 很大程度上是为了兼容早期的主从复制逻辑(Binlog 格式问题)。

    2. RR 真的不能解决幻读吗?

    MySQL 的 RR 通过间隙锁(Gap Lock) 在很大程度上解决了幻读:

    • 普通 SELECT 靠 MVCC 让你"看不见"新插入的数据
    • SELECT … FOR UPDATE 靠间隙锁"锁住"插入位置,让别人插不进来

    只有在"先快照读,再当前读更新"这种特殊场景下,才会触发 MVCC 的弱点。

    3. 为什么大厂往往把默认级别改为 RC?

    • 死锁概率:RR 下的间隙锁极大增加了死锁概率
    • 并发性能:RC 没有间隙锁,且有"半一致性读"优化,吞吐量更高
    • 业务容忍度:大多数互联网业务并不在乎那一点"不可重复读",或者可以通过乐观锁等业务手段补偿

    总结

    概念要点
    隐藏字段 DB_TRX_ID + DB_ROLL_PTR 构成版本追踪基础
    Undo Log 存储历史版本,支撑回滚和 MVCC
    Read View 决定事务能看到哪些版本
    RC vs RR Read View 生成时机不同,决定是否可重复读
    间隙锁 MySQL RR 级别解决幻读的关键机制
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 深入理解 MySQL:MVCC - 从版本链到事务隔离
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!