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

多版本并发控制MVCC

 MVCC(Multi-Version Concurrency Control,多版本并发控制)。是一个在数据库管理系统中用于处理并发控制的核心技术。理解它对于深入掌握数据库(尤其是 InnoDB、PostgreSQL 等)的工作原理至关重要。

1. 什么是 MVCC?

MVCC 的全称是 多版本并发控制

核心思想:在数据库中,同一份数据可以保留多个历史版本。当事务需要读取数据时,它会根据一定的规则(比如事务的开始时间)看到一个特定的、一致性的“快照”(Snapshot),而不是直接读取最新的、可能还未提交的数据。写操作则会创建一个新版本的数据。

你可以把它想象成一个高效的版本控制系统(如 Git):

  • 读操作:就像 git checkout 到某个特定的 commit 版本,你看到的是那个时间点的完整项目状态,即使之后有新的提交,你的视图也不会变。
  • 写操作:就像创建一个新的 commit,它不会覆盖旧的 commit,而是在旧版本的基础上生成一个新版本。

通过这种方式,读操作和写操作可以不再互相阻塞,从而极大地提高了数据库的并发性能。


2. 为什么需要 MVCC?

在传统的数据库并发控制中,主要使用两种机制:

  1. 锁机制

    • 读-写冲突:当一个事务在读取一行数据时,会给它加上共享锁(S锁)。另一个事务如果想修改这行数据,需要加排他锁(X锁),但 S 锁和 X 锁互斥,所以写事务必须等待读事务完成。反之亦然,写事务会阻塞读事务。
    • 问题:并发度低。读和写操作串行化,性能很差。
  2. 基于时间戳的排序

    • 所有操作按时间戳排序执行,如果操作冲突,则回滚其中一个事务。
    • 问题:事务冲突率高,回滚频繁,性能同样不理想。

MVCC 的出现就是为了解决这些问题,它提供了一种“乐观”的并发控制方式:

  • 读写不冲突:读数据(快照读)不会阻塞写数据,写数据也不会阻塞读数据。这是 MVCC 最大的优势。
  • 非锁定读:大多数情况下,普通的 SELECT 查询不需要加锁,避免了锁的开销和死锁的风险。
  • 实现事务隔离:MVCC 是实现数据库事务隔离级别(特别是 READ COMMITTED 和 REPEATABLE READ)的基础。

3. MVCC 是如何工作的?

MVCC 的实现依赖于三个关键组件:隐藏列Undo Log 和 Read View。我们以最经典的 MySQL InnoDB 存储引擎为例来讲解。

a. 隐藏列

InnoDB 会为每一行数据额外添加三个隐藏的字段:

  • DB_TRX_ID (6字节): 最后修改该行的事务ID。记录了最后一次对这行记录进行 INSERT 或 UPDATE 的事务ID。每次事务修改一行,这个字段都会被更新。
  • DB_ROLL_PTR (7字节): 回滚指针。它指向该行上一个版本的数据在 Undo Log 中的位置。通过这个指针,可以形成一个“版本链”,把一个数据行的所有历史版本串联起来。
  • DB_ROW_ID (6字节): 隐藏的行ID。一个单调递增的ID,当表没有显式主键时,InnoDB会用它来生成一个聚集索引。

版本链示例
假设一行数据被事务 10、事务 20 依次修改。

  1. 初始状态:事务 10 插入一行数据。
    • DB_TRX_ID = 10
    • DB_ROLL_PTR = null (因为是第一个版本)
  2. 事务 20 修改:事务 20 更新了这行数据。
    • InnoDB 不会直接覆盖旧数据,而是:
      1. 将旧版本的数据(DB_TRX_ID=10 的版本)复制到 Undo Log 中。
      2. 在原位置创建一个新版本的数据行。
      3. 更新新版本的字段:DB_TRX_ID = 20
      4. 更新新版本的 DB_ROLL_PTR,让它指向 Undo Log 中旧版本的位置。
    • 现在,通过新版本的 DB_ROLL_PTR,我们可以找到旧版本,形成一条 版本链最新版本(20) -> 旧版本(10)
b. Undo Log

Undo Log 主要有两个作用:

  1. 事务回滚:当一个事务需要回滚时,可以利用 Undo Log 中记录的旧版本数据,将数据恢复到修改之前的状态。
  2. 构建版本链:如上所述,它存储了数据行的历史版本,是 MVCC 实现多版本的关键。当需要读取某个历史版本时,就可以从这里获取。
c. Read View(读视图)

Read View 是事务在执行快照读(普通的 SELECT)时,动态生成的一个“可见性判断”标准。它决定了当前事务能看到版本链上的哪个版本。

Read View 主要包含以下几个重要属性:

  • creator_trx_id: 创建该 Read View 的事务的 ID。
  • trx_ids: 创建 Read View 时,当前系统中所有活跃的(未提交的)读写事务的 ID 列表。
  • up_limit_idtrx_ids 列表中事务 ID 的最小值。如果版本链上某个版本的 DB_TRX_ID 小于 up_limit_id,则表示这个版本在创建 Read View 之前已经提交,所以对当前事务是可见的
  • low_limit_id: 创建 Read View 时,系统应该分配给下一个事务的 ID。如果版本链上某个版本的 DB_TRX_ID 大于或等于 low_limit_id,则表示这个版本是在创建 Read View 之后才开启的事务中修改的,所以对当前事务是不可见的

4. MVCC 如何解决并发问题?

现在,我们把这三个组件结合起来,看看一个 SELECT 语句是如何利用 MVCC 找到它应该看到的数据版本的。我们以 InnoDB 的 REPEATABLE READ(可重复读)隔离级别为例。

核心判断流程
当一个事务(假设 ID 为 T1)执行 SELECT 时,它会获取一个 Read View。然后,它会从版本链的最新版本开始,逐个版本地应用以下规则,直到找到一个可见的版本:

  1. 检查 DB_TRX_ID 是否是自己创建的?

    • 如果 DB_TRX_ID == creator_trx_id,说明这行数据是本事务自己修改的,可见
  2. 检查 DB_TRX_ID 是否小于 up_limit_id

    • 如果 DB_TRX_ID < up_limit_id,说明修改这个版本的事务在当前事务开始前就已经提交了,可见
  3. 检查 DB_TRX_ID 是否大于或等于 low_limit_id

    • 如果 DB_TRX_ID >= low_limit_id,说明修改这个版本的事务是在当前事务开始之后才启动的,不可见。需要根据 DB_ROLL_PTR 去 Undo Log 中查找上一个版本,然后重复整个判断流程。
  4. 检查 DB_TRX_ID 是否在 trx_ids 列表中?

    • 如果 up_limit_id <= DB_TRX_ID < low_limit_id,则需要判断 DB_TRX_ID 是否在活跃事务列表 trx_ids 中。
    • 如果在:说明修改这个版本的事务在当前事务创建 Read View 时还未提交,不可见。需要去 Undo Log 中找上一个版本。
    • 如果不在:说明修改这个版本的事务在当前事务创建 Read View 时已经提交了,可见

最终:如果遍历完整个版本链都找不到可见的版本,说明这行数据对当前事务是不可见的(比如被其他事务删除了)。

REPEATABLE READ vs READ COMMITTED 的关键区别
  • REPEATABLE READ (可重复读)

    • 事务中第一次执行 SELECT 时,会创建一个 Read View,之后该事务内的所有 SELECT 都复用这个 Read View
    • 效果:确保了在同一个事务中,多次读取同一数据的结果是一致的,因为判断可见性的标准(Read View)从未改变。这就是“可重复读”的由来。
  • READ COMMITTED (读已提交)

    • 事务中每次执行 SELECT 时,都会重新创建一个新的 Read View
    • 效果:每次读取都能看到其他已提交事务所做的最新修改。因为每次的 Read View 都是最新的,up_limit_id 和 trx_ids 都会更新,所以之前不可见的版本可能就变得可见了。

5. MVCC 的优缺点

优点
  1. 高并发性:读写操作不阻塞,极大地提高了数据库的并发读写性能。
  2. 非锁定读:避免了读操作加锁带来的开销和死锁风险。
  3. 实现一致性读:为不同隔离级别提供了基础,保证了事务的隔离性。
缺点
  1. 存储空间开销:需要维护多个版本的数据,Undo Log 会占用额外的存储空间。对于长事务或更新频繁的表,Undo Log 可能会变得非常大。
  2. 管理开销:需要额外的逻辑来管理版本链、创建和判断 Read View,增加了数据库的复杂性。
  3. 行版本清理:需要后台线程(如 InnoDB 的 Purge 线程)定期清理已经不再需要的旧版本数据(即没有事务再需要访问它们),否则 Undo Log 会无限增长。这个清理过程本身也消耗资源。
  4. 并非万能:MVCC 主要解决的是 SELECT 的并发问题。对于 UPDATEDELETE 之间的冲突,仍然需要使用(比如行锁、间隙锁、Next-Key Locks)来保证数据的一致性和防止幻读。

6. MVCC 与隔离级别的关系

隔离级别MVCC 如何工作能解决的问题
READ UNCOMMITTED (读未提交)基本不使用 MVCC。直接读取最新的数据,即使它未提交。
READ COMMITTED (读已提交)每次 SELECT 都创建新的 Read View。只能读到已提交的数据。解决脏读
REPEATABLE READ (可重复读)事务中第一次 SELECT 创建 Read View,后续复用。保证同一事务内多次读取结果一致。解决脏读、不可重复读
(在 InnoDB 中,结合 Next-Key Locks 还能解决幻读)
SERIALIZABLE (可串行化)基本不使用 MVCC 的快照读。所有 SELECT 语句都会隐式地转换为 SELECT ... LOCK IN SHARE MODE,即加共享锁。读写操作都互相阻塞。解决所有并发问题(脏读、不可重复读、幻读)

7. 总结

MVCC 是一种优雅而强大的并发控制技术,其精髓在于“用空间换时间,用版本换锁”。

  • 核心:通过为数据维护多个版本,让读操作访问历史快照,写操作创建新版本。
  • 关键组件:隐藏列(DB_TRX_IDDB_ROLL_PTR)构建版本链,Undo Log 存储历史版本,Read View 定义可见性规则。
  • 目的:实现读写不阻塞,提高并发性能,并作为实现数据库事务隔离级别的基础。
  • 应用:广泛应用于现代主流数据库,如 MySQL InnoDBPostgreSQLOracle 等。
http://www.dtcms.com/a/354970.html

相关文章:

  • 黑马点评|项目日记(day02)
  • C#和Lua相互访问
  • 基于金庸武侠小说人物关系设计的完整 SQL 语句,包括数据库创建、表结构定义和示例数据插入
  • Docker 详解+示例
  • map底层的数据结构是什么,为什么不用AVL树
  • 机器学习回顾(一)
  • 陪诊小程序系统开发:搭建医患之间的温暖桥梁
  • Scrapy 基础介绍
  • 安全运维——系统上线前安全检测:漏洞扫描、系统基线与应用基线的全面解析
  • lwIP MQTT 心跳 Bug 分析与修复
  • 边缘计算(Edge Computing)+ AI:未来智能世界的核心引擎
  • HarmonyOS 组件与页面生命周期:全面解析与实践
  • Paimon——官网阅读:Flink 引擎
  • 【秋招笔试】2025.08.27华为秋招研发岗真题
  • 【新启航】3D 逆向抄数效率提升:自动化拼接工具与智能建模能力如何缩短 50% 项目周期
  • 聚类准确率计算——标签映射(Kuhn-Munkres匈牙利算法)问题的解决(详细并附完整代码)
  • 大模型RAG(Retrieval-Augmented Generation)
  • Python日期计算完全指南:从上周五到任意日期的高效计算
  • Cubemx+Vscode安装与环境配置
  • 聚焦建筑能源革新!安科瑞 “光储直柔” 方案护航碳中和目标实现
  • 162.在 Vue 3 中使用 OpenLayers 解析 GeoJSON 并为每个 Feature 填充渐变色
  • 如何调试一个EVM合约:实战操作 + 常见报错说明
  • 2025年第五届电子信息工程与计算机科学国际会议(EIECS 2025)
  • IO的最大输出速度
  • Maven 项目单元测试实战指南:从环境搭建到问题排查全解析
  • 一天认识一个神经网络之--CNN卷积神经网络
  • Linux系统之----命名管道模拟实现客户端、服务器
  • ImageToPromptAI-AI图像转提示词生成器
  • ftp命令批量删除服务器上的文件
  • 关于我在一个优惠券系统中rocketMQ消息幂等性自定义注解的处理