7天读懂MySQL|特别篇:MVCC详解

有评论在看专栏《7天读懂MYSQL》时,对MVCC有疑惑,今天就写一篇MVCC详解,开搞!

1. MVCC概述

1.1 什么是MVCC

MVCC(Multi-Version Concurrency Control,多版本并发控制)是InnoDB存储引擎实现事务隔离级别的核心机制,它通过为每行数据维护多个版本,使读操作无需加锁,从而实现高并发下的数据一致性。

1.2 为什么需要MVCC

在传统锁机制下:

  • 读操作需要获取共享锁,阻塞写操作
  • 写操作需要获取排他锁,阻塞所有其他操作
  • 高并发场景下,锁竞争导致性能急剧下降

MVCC解决的核心问题:

  • 实现"无锁读":读操作直接访问数据的历史版本,无需等待
  • 提升并发性能:读操作不阻塞写操作,写操作不阻塞读操作
  • 保障事务隔离性:为READ COMMITTEDREPEATABLE READ提供实现基础

2、⭐MVCC的核心数据结构

2.1 隐藏字段

InnoDB在每行数据中添加三个隐藏字段来支持MVCC:

  • DB_TRX_ID(6字节):记录最后一次修改该行数据的事务ID。这个ID是全局递增的,每个新事务都会获得一个更大的ID。

  • DB_ROLL_PTR(7字节):回滚指针,指向该行数据的上一个版本在Undo Log中的位置。通过这个指针,可以遍历整条版本链。

  • DB_ROW_ID(6字节):当表没有定义主键时,InnoDB会自动生成这个隐藏主键。对于有主键的表,这个字段仍然存在但不一定使用。

2.2 ReadView(读视图)

ReadView是MVCC实现快照读的关键数据结构,它定义了事务能看到的数据版本范围。

  • m_low_limit_id:高水位线,事务ID大于等于这个值的事务对当前ReadView不可见

  • m_up_limit_id:低水位线,事务ID小于这个值的事务对当前ReadView可见

  • m_creator_trx_id:创建此ReadView的事务ID

  • m_ids:ReadView创建时,系统中所有活跃(未提交)事务的ID列表

2.3 Undo Log(回滚日志)

Undo Log不仅用于事务回滚,更是MVCC版本链的物理存储。每个修改操作都会创建对应的Undo Log记录。

Undo Log的关键特性

  1. 类型分类

    • INSERT Undo Log:记录插入操作,只用于回滚,事务提交后可删除

    • UPDATE/DELETE Undo Log:记录更新/删除操作,用于回滚和MVCC版本链

  2. 版本链构建

    • 每次更新操作都会创建新的Undo Log记录

    • 新的Undo Log记录指向上一个版本

    • 通过DB_ROLL_PTR可以遍历整个版本链

  3. 生命周期管理

    • 活跃事务引用的版本必须保留

    • 不再被任何事务需要的版本由Purge线程清理

3、⭐MVCC的工作流程

3.1 快照读的流程(SELECT)

快照读是指普通的SELECT查询,读取数据的历史版本,不需要加锁。

快照读的关键步骤

  1. ReadView生成

    • RC级别:每次SELECT都生成新ReadView

    • RR级别:第一次SELECT生成ReadView,后续查询复用

  2. 版本定位

    • 通过索引找到目标记录

    • 读取聚簇索引中的最新版本

  3. 可见性判断

    • 使用ReadView的可见性规则判断当前版本是否可见

    • 如果不可见,沿版本链回溯查找可见版本

  4. 结果构建

    • 将可见版本加入结果集

    • 继续处理下一条记录

3.2 当前读的流程(UPDATE/DELETE/INSERT)

当前读需要读取数据的最新版本,并对读取的记录加锁,确保数据的一致性。

当前读的关键特点

  1. 必须加锁:防止其他事务并发修改同一数据

  2. 读取最新版本:忽略历史版本,直接操作当前数据

  3. 创建新版本:每次修改都会创建新版本,旧版本进入Undo Log

  4. 隐藏字段更新

    • DB_TRX_ID设置为当前事务ID

    • DB_ROLL_PTR指向新创建的Undo Log

4、不同隔离级别下的MVCC

4.1 Read Committed(读已提交)

RC级别下,事务能读取到其他事务已提交的最新数据。

核心特点

  • 每次SELECT都生成新的ReadView

  • 能看到其他事务最新提交的修改

  • 可能发生"不可重复读"现象,同一事务中多次查询结果不一致

RC级别的ReadView生成

  1. 每次执行SELECT时创建新的ReadView

  2. ReadView反映查询时刻的数据库状态

  3. 可能看到查询前已提交的其他事务修改

4.2 Repeatable Read(可重复读)

RR级别下,事务在整个过程中看到的数据保持一致。

核心特点

  • 第一次SELECT时生成ReadView,后续查询复用

  • 整个事务期间看到相同的数据快照,同一事务中多次查询结果一致

RR级别的关键机制

  1. ReadView固化:事务第一次查询时生成ReadView,之后一直复用

  2. 版本链回溯:如果最新版本不可见,沿版本链查找可见的历史版本

  3. 一致性快照:整个事务期间看到相同的数据状态

4.3 RC与RR下MVCC对比

五、MVCC与锁的协同工作机制

5.1 快照读 vs 当前读

MVCC与锁机制协同工作,为不同类型的操作提供适当的并发控制。

sql 复制代码
-- 快照读(Snapshot Read):使用MVCC,无锁
SELECT * FROM users WHERE age > 20;

-- 当前读(Current Read):使用锁机制
SELECT * FROM users WHERE age > 20 FOR UPDATE;
SELECT * FROM users WHERE age > 20 LOCK IN SHARE MODE;

-- DML操作内部使用当前读
UPDATE users SET status = 'active' WHERE age > 20;
DELETE FROM users WHERE age > 20;

5.2 Next-Key Lock:防止幻读的锁机制

在RR隔离级别下,InnoDB使用Next-Key Lock来防止幻读现象。

幻读问题

  • 事务A读取某个范围的数据

  • 事务B在该范围内插入新数据并提交

  • 事务A再次读取,发现多出了"幻影行"

Next-Key Lock的组成

  1. 记录锁(Record Lock):锁定索引记录

  2. 间隙锁(Gap Lock):锁定索引记录之间的间隙

Next-Key Lock的工作机制

  1. 锁定范围:锁定查询涉及的所有记录和间隙

  2. 防止插入:其他事务无法在锁定范围内插入新记录

  3. 允许读取:快照读仍可使用MVCC访问数据

  4. 范围控制:只锁定必要的范围,最小化对并发的影响

6. MVCC的深度误区与实战解决方案

误区1:MVCC可以解决幻读问题

真相 :MVCC在RR级别下保证了可重复读 ,但不能解决幻读

解决方案

  • 使用SELECT FOR UPDATE(当前读)触发Next-Key Lock
  • 通过间隙锁防止新行插入

误区2:MVCC完全避免锁竞争

错误认知:MVCC让所有操作都无锁,不会产生锁竞争。

事实

  • 快照读确实无锁,但当前读仍需加锁

  • 写操作之间的锁竞争仍然存在

  • 二级索引更新可能产生额外的锁

解决方案

  1. 合理设计索引,减少锁范围

  2. 将大事务拆分为小事务,减少锁持有时间

  3. 使用乐观锁机制处理高并发更新

误区3:MVCC不会产生性能问题

错误认知:MVCC只有好处,没有性能代价。

事实

  • 版本链过深会降低查询性能

  • Undo Log占用额外存储空间

  • Purge操作消耗CPU和IO资源

解决方案

  1. 监控版本链深度:SHOW ENGINE INNODB STATUS

  2. 避免长事务,减少历史版本保留时间

  3. 定期检查Undo Log空间使用情况

误区4:所有SELECT都是快照读

错误认知:所有SELECT语句都使用MVCC机制。

事实

  • 使用FOR UPDATELOCK IN SHARE MODE的SELECT是当前读

  • 在事务中混合使用快照读和当前读可能导致数据不一致

解决方案

  1. 明确区分业务场景,选择合适的读取方式

  2. 避免在同一事务中混用两种读取方式

  3. 对需要强一致性的查询使用当前读

7. 总结

MVCC的核心价值与深度理解

  1. MVCC不是简单的"多版本存储"
    • 它是通过DB_TRX_IDDB_ROLL_PTRReadView的精密配合实现的
    • 事务ID是MVCC的基石,版本链是数据结构,ReadView是可见性判断的核心
  2. MVCC与隔离级别的关系
    • RC:每次查询生成新ReadView,看到最新已提交数据
    • RR:只在第一次查询生成ReadView,保证可重复读
  3. MVCC的性能影响
    • 读不阻塞写,写不阻塞读,提高并发性能
    • 但版本链过长会导致性能下降,需要合理调优
相关推荐
计算机毕设指导61 小时前
基于微信小程序技术校园拼车系统【源码文末联系】
java·spring boot·mysql·微信小程序·小程序·tomcat·maven
hhzz2 小时前
Springboot项目中使用EasyPOI操作Excel(详细教程系列4/4)
java·spring boot·后端·spring·excel·poi·easypoi
北邮刘老师2 小时前
从SEO到ADO:智能体时代的流量密码
服务器·网络·数据库·人工智能·大模型·智能体·智能体互联网
javachen__2 小时前
mysql系统级文件损坏修复
数据库·mysql
云和数据.ChenGuang2 小时前
达梦数据库部署安装故障一
数据库·oracle·达梦·信创·达梦数据库
JaguarJack2 小时前
2026 年 PHP 函数式编程 优势与实际应用
后端·php·服务端
倔强的石头1062 小时前
场景化落地指南——金仓时序数据库在关键行业的应用实践
数据库·时序数据库·kingbase
xj7573065332 小时前
《精通Django》 第7章 高级视图和URL配置
数据库·django·sqlite
海棠AI实验室2 小时前
第2篇:本地目录与资产标准(把“素材—文案—对话—上架”变成可追溯的生产线)
数据库·资产