当前位置: 首页 > news >正文

MySQL学习之MVCC多版本并发控制

        在数据库并发场景中,如何平衡一致性与性能始终是核心难题。当多个事务同时读写数据时,可能出现脏读、不可重复读、幻读等问题。MySQL 的 InnoDB 存储引擎通过 MVCC(多版本并发控制)机制,在不加锁的情况下巧妙解决了这些问题,成为其高性能并发的关键。

        MVCC(Multi-Version Concurrency Control,多版本并发控制)是 InnoDB 实现事务隔离级别的核心机制。它通过为数据记录保存多个版本,允许读写操作不互相阻塞,从而在高并发场景下提升数据库性能。简单来说:读操作可以访问数据的历史版本,无需等待写操作释放锁;写操作也无需阻塞读操作,二者通过 “版本” 实现隔离。

        在 MVCC 出现前,数据库解决并发问题主要依赖锁机制:读加共享锁(S 锁),写加排他锁(X 锁)。但这种方式会导致 “读阻塞写、写阻塞读”,严重影响并发性能。MVCC 的出现解决了这一痛点,其核心作用包括:

(1)读写不冲突:读操作无需加锁,直接读取历史版本;写操作仅锁定当前版本,不阻塞读。

(2)支持多隔离级别:通过控制 “可见版本” 的规则,实现 REPEATABLE READ(可重复读)、READ COMMITTED(读已提交)等隔离级别(InnoDB 默认 REPEATABLE READ)。

(3)解决并发问题:避免脏读(读取未提交的数据)、不可重复读(同一事务内多次读取结果不一致),配合间隙锁可解决幻读。

        InnoDB 的 MVCC 通过隐藏列undo 日志Read View三大组件协同实现,缺一不可。它们的作用分别是:

        (一)隐藏列:

        InnoDB 为每个数据行添加了 3 个隐藏列(非用户定义),用于标记版本信息:

(1)DB_TRX_ID:6 字节,记录最后一次修改该记录的事务 ID(事务开始时由 InnoDB 分配的唯一 ID)。

(2)DB_ROLL_PTR:7 字节,回滚指针,指向该记录的上一个版本(存储在 undo 日志中)。

(3)DB_ROW_ID:6 字节,若表无主键或唯一索引,InnoDB 会用该列生成聚簇索引(可选)。

例如,一行数据的实际存储结构如下(简化):

id(用户定义)nameDB_TRX_IDDB_ROLL_PTR
1张三100指向 undo 日志版本 1

(二)undo 日志:

        undo 日志(回滚日志)是存储数据历史版本的地方。当事务修改数据时,InnoDB 会先将旧版本数据写入 undo 日志,再更新当前记录的 DB_TRX_ID 和 DB_ROLL_PTR(指向 undo 日志中的旧版本)。undo 日志分为两类:

(1)insert undo:记录插入操作的旧版本,事务提交后可直接删除(插入的数据在事务外不可见)。

(2)update undo:记录更新 / 删除操作的旧版本,需保留至没有事务需要访问这些版本(由 purge 线程清理)。

        通过 undo 日志,InnoDB 可形成一条 “版本链”:当前记录 → DB_ROLL_PTR → 上一版本(undo 日志) → ... → 最初版本。

(三)Read View:

        Read View(读视图)是一个动态生成的 “快照”,用于判断当前事务能看到哪个版本的数据。它包含 4 个核心字段:

(1)m_ids:当前活跃(未提交)的事务 ID 列表。

(2)min_trx_id:m_ids 中的最小事务 ID。

(3)max_trx_id:下一个将被分配的事务 ID(并非 m_ids 中的最大值)。

(4)creator_trx_id:当前事务的 ID。

其有一套可见性判断规则,例如(假设某版本的 DB_TRX_ID 为 trx_id):

(1)若trx_id == creator_trx_id:当前事务修改的版本,可见。

(2)若trx_id < min_trx_id:该版本由已提交事务生成,可见。

(3)若trx_id > max_trx_id:该版本由未来事务生成,不可见。

(4)若min_trx_id ≤ trx_id ≤ max_trx_id,trx_id在m_ids中,则事务未提交,版本不可见。反之,若trx_id不在m_ids中,则代表事务已提交,版本可见。

        通过这套规则,Read View 能精准筛选出当前事务可访问的版本。

        MVCC的主要优点有:
(1)读写并发性能高:读不加锁,写仅锁当前版本,避免 “读写互斥”,适合读多写少场景。
(2)事务隔离性好:通过版本控制天然支持读已提交和可重复读隔离级别,无需复杂锁机制。
(3)减少锁竞争:避免了共享锁(S 锁)的使用,降低死锁概率。
但其也有缺点,例如:
(1)存储开销大:undo 日志需保存多个历史版本,可能占用较多磁盘空间。
(2)性能损耗:版本链遍历和 Read View 判断会增加 CPU 开销;undo 日志的清理(purge 线程)也需额外资源。
(3)幻读问题:MVCC 在可重复读隔离级别下无法完全解决幻读(需配合间隙锁,InnoDB 默认开启)。

        下面,我们通过实际 SQL 操作,观察 MVCC 在不同隔离级别下的表现(以 InnoDB 为例):

-- 创建一个用户表,并插入数据
CREATE TABLE `user` (`id` int PRIMARY KEY,`name` varchar(10) NOT NULL
) ENGINE=InnoDB;INSERT INTO `user` VALUES (1, '张三');

        在可重复读隔离级别(默认级别)下,其运行如下:

时间事务 A(ID=100)

事务 B(ID=200)

T1BEGIN;BEGIN;
T2(未操作)SELECT * FROM user WHERE id=1; -- 结果:name=' 张三 '(DB_TRX_ID=0,初始版本)
T3UPDATE user SET name=' 李四 ' WHERE id=1; -- DB_TRX_ID=100,DB_ROLL_PTR 指向旧版本(未操作)
T4(未提交)SELECT * FROM user WHERE id=1; -- 结果:仍为 ' 张三 '(因可重复读隔离级别的 Read View 在 T1 生成,看不到事务 A 的未提交版本)
T5COMMIT;(未操作)
T6(已提交)

SELECT * FROM user WHERE id=1; -- 结果:仍为 ' 张三 '(可重复读隔离级别的 Read View 不变,看不到事务 A 的提交版本)

        可以看到,在可重复读隔离级别下,事务 B 全程看到的是 T1 时刻的版本,符合要求。而下面将隔离级别换成读已提交级别:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

        然后运行如下:

时间

事务 A(ID=100)事务 B(ID=200)
T1BEGIN;BEGIN;
T2(未操作)SELECT * FROM user WHERE id=1; -- 结果:' 张三'
T3UPDATE user SET name=' 李四 ' WHERE id=1;(未操作)
T4(未提交)SELECT * FROM user WHERE id=1; -- 结果:' 张三 '(事务 A 未提交,版本不可见)
T5COMMIT;(未操作)
T6(已提交)SELECT * FROM user WHERE id=1; -- 结果:' 李四 '(RC 的 Read View 在 T6 重新生成,看到已提交版本)

        可以看出,在读已提交级别下,事务 B 在 T6 看到了事务 A 提交的新版本,符合其 “读已提交” 特性。

        可以看出在读已提交和可重复读2个级别中,MVCC的主要差异在于在可重复读级别中,事务开始时生成一次 Read View,事务内查询结果一致;而读已提交级别中,每次查询时生成新的 Read View,能看到其他事务已提交的更新。

        MVCC 是 InnoDB 并发控制的灵魂,通过隐藏列、undo 日志和 Read View 的协同,实现了 “读写不阻塞” 的高效并发,理解 MVCC 能帮助我们写出更符合隔离级别的 SQL。

http://www.xdnf.cn/news/1239661.html

相关文章:

  • python常用数据类型
  • 13.Redis 的级联复制
  • 03.一键编译安装Redis脚本
  • sqli-labs:Less-23关卡详细解析
  • 【运维基础】Linux 硬盘分区管理
  • 数据集相关类代码回顾理解 | StratifiedShuffleSplit\transforms.ToTensor\Counter
  • Corrosion2靶机练习笔记
  • 选择排序原理与C语言实现详解
  • 第15届蓝桥杯Scratch图形化国赛初/中级组2024年9月7日真题
  • 【LeetCode刷题指南】--对称二叉树,另一颗树的子树
  • 【量化交易】日内交易有效特征因子
  • Socket编程——TCP协议
  • 智慧社区(六)——社区居民人脸识别功能实现详解:从腾讯 API 集成到模拟验证
  • CMake 命令行参数完全指南(2)
  • C++入门自学Day5-- C/C++内存管理(续)
  • 控制建模matlab练习08:根轨迹
  • 【图像处理基石】如何使用deepseek进行图像质量的分析?
  • pycharm上如何添加conda环境
  • 当Windows远程桌面出现“身份验证错误。要求的函数不受支持”的问题
  • [硬件电路-150]:数字电路 - 数字电路与模拟电路的异同
  • Ollama模型库模型下载慢完美解决(全平台)
  • 算法讲解--最大连续1的个数
  • RSA 解密逻辑
  • 【从零开始学习Redis】初识Redis
  • 第13章 文件输入/输出
  • 网关与路由器的区别
  • 【MySQL】MySQL中锁有哪些?
  • 常见的框架漏洞(Thinkphp,spring,Shiro)
  • 2025年测绘程序设计比赛--基于统计滤波的点云去噪(已获国特)
  • 波士顿房价预测工具 - XGBoost实现