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

MVCC多版本并发控制

MVCC

MVCC 是 Inno DB 实现高并发、高性能事务处理的核心机制之一,尤其对于 READ COMMITTED 和 REPEATABLE READ 这两个常用的事务隔离级别至关重要。它的核心思想是:通过保存数据在某个时间点的多个版本来实现非锁定读(快照读),从而避免读写操作相互阻塞,提高数据库的并发能力。

MVCC要解决的核心问题

在传统的锁机制(如共享锁、排他锁)下:

  1. 读操作阻塞写操作:当一个事务持有读锁(S锁)时,其他事务无法获取写锁(X锁)进行修改,导致写操作被阻塞。
  2. 写操作阻塞读操作:当一个事务持有写锁(X锁)时,其他事务无法获取读锁(S锁)进行读取,导致读操作被阻塞。
  3. 锁竞争激烈:高并发场景下,锁竞争会成为性能瓶颈。

MVCC的目标就是让读操作(通常是 SELECT)不再需要加锁,从而避免上述阻塞,极大提高并发性能。写操作(也就是 INSERT、UPDATE、DELETE)仍然需要适当的锁(通常是行锁)来保证数据的一致性和隔离性。


MVCC的核心思想与关键组件

MVCC通过为数据库中的每一行记录维护多个历史版本来实现。当一个事务需要读取数据的时候,它看到的是在事务开始时或语句开始时已提交的数据版本(一个“快照”),而不是最新的、可能正在被其他事务修改的数据。这依赖于以下几个关键机制:

  1. 隐藏的系统字段:

    • InnoDB 在存储每一行记录时,会自动添加两个(有时三个)隐藏的系统字段:

      • DB_TRX_ID (6 Bytes): 事务ID。 记录最后一次修改(INSERT 或 UPDATE) 该行记录的事务ID。删除操作在内部被视为一种特殊的 UPDATE(设置删除标记)。

      • DB_ROLL_PTR (7 Bytes): 回滚指针。 指向该行记录在 Undo Log 中的上一个历史版本的指针。通过这个指针可以构建出一条记录的所有历史版本链表(版本链)。

      • DB_ROW_ID (6 Bytes): 行ID (隐含主键)。 如果表没有定义主键,InnoDB 会自动生成一个单调递增的行 ID 作为聚簇索引。注意: 这个字段主要是为了构建索引,在 MVCC 机制中作用相对次要。

  2. Undo Log (回滚日志):

    • Undo Log 是 MVCC 实现多版本的核心存储。当事务修改(UPDATE 或 DELETE)一行数据时:

      • 会先将该行数据的当前版本(修改前的状态)拷贝到 Undo Log 中。

      • 然后更新内存中该行数据的 DB_TRX_ID 为当前事务的 ID。

      • 更新内存中该行数据的 DB_ROLL_PTR 指向刚刚写入 Undo Log 的历史版本。

    • 对于 INSERT 操作,Undo Log 只需要记录新插入行的主键信息,以便在事务回滚时删除它。

    • Undo Log 中的历史版本通过 DB_ROLL_PTR 指针连接起来,形成一个按修改时间倒序排列的版本链。链表的头部是当前最新的版本。

  3. Read View (读视图):

    • Read View 是 MVCC 机制中判断数据版本可见性的关键数据结构。当事务执行快照读(普通的 SELECT)时,会生成一个 Read View(具体生成时机取决于隔离级别)。

    • Read View 本质上是事务启动时(或语句启动时,取决于隔离级别)数据库系统的一个快照,它包含以下重要信息:

      • m_ids: 生成 Read View 时,当前系统中所有活跃(已启动但未提交)事务的事务ID列表

      • min_trx_idm_ids 中的最小事务ID

      • max_trx_id: 生成 Read View 时,系统应该分配给下一个新事务的事务ID(即当前最大事务ID + 1)。

      • creator_trx_id创建该 Read View 的事务自身的事务ID(对于只读事务,该ID可能为0)。


MVCC如何工作?——数据可见性规则

当一个事务执行快照读操作时,对于要访问的每一行数据,InnoDB会沿着该行数据的版本链,利用当前事务的 Read View 来判断哪个版本的数据对该事务是可见的。判断规则如下(按顺序检查):

  1. 检查最新版本的 DB_TRX_ID 如果该值等于 creator_trx_id(即该行最后是被自己修改的),可见

  2. 检查 DB_TRX_ID 是否小于 min_trx_id 如果 DB_TRX_ID < min_trx_id,说明该版本是在生成 Read View 之前就已经提交了的,可见

  3. 检查 DB_TRX_ID 是否大于等于 max_trx_id 如果 DB_TRX_ID >= max_trx_id,说明该版本是由在生成 Read View 之后才启动的事务修改的,不可见

  4. 检查 DB_TRX_ID 是否在 m_ids 列表中:

    • 如果在,说明修改该版本的事务在生成 Read View 时还处于活跃状态(未提交)不可见

    • 如果不在,说明修改该版本的事务在生成 Read View 时已经提交了,可见

  5. 沿着版本链回溯: 如果当前版本不可见,则通过 DB_ROLL_PTR 指针找到上一个历史版本,重复步骤 1-4 进行判断,直到找到一个可见的版本或到达链尾(表示该行对该事务完全不可见)。

简单总结可见性规则:数据行版本的事务ID(DB_TRX_ID)必须满足以下条件之一才对该Read View可见:

  • 等于创建Read View的事务自身ID(自己改的)
  • 小于Read View中的min_trx_id(老早以前提交的)
  • 不在Read View的活跃事务列表m_ids中,并且小于max_trx_id(在Read View创建时已经提交的)

MVCC在不同隔离级别下的行为差异

MVCC的行为与事务隔离级别紧密相关,主要体现在Read View的生成时机上:

  1. READ COMMITTED (RC - 读已提交):

    • Read View 生成时机: 每次执行 SELECT 语句时都会生成一个新的 Read View。

    • 效果: 每次读取都能看到最新提交的数据。这解决了脏读(因为只读已提交的),但导致了不可重复读(因为两次读之间如果有其他事务提交了修改,新生成的Read View能看到这些修改)。

    • 举例: 事务A第一次查询得到值X。事务B修改X为Y并提交。事务A第二次查询(生成新的Read View)就能看到Y。

  2. REPEATABLE READ (RR - 可重复读 - InnoDB默认):

    • Read View 生成时机: 只在事务中第一次执行快照读(SELECT)时生成一个 Read View,并在整个事务期间都使用这个相同的 Read View。

    • 效果: 在整个事务中,多次读取同一行数据,看到的都是第一次读时(或事务开始时)已经提交的数据版本。这解决了脏读和不可重复读。

    • 幻读的特殊处理: 虽然RR级别下快照读通过固定的Read View避免了幻读(因为新插入的数据版本的事务ID大于max_trx_id,不可见),但当前读SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEUPDATEDELETE)会看到最新的已提交数据,可能感知到新插入的行(幻读)。InnoDB 通过 Next-Key Locking(间隙锁) 来防止其他事务在事务A扫描的范围内插入新行,从而在当前读层面也解决了幻读问题。所以,InnoDB 的 RR 级别通过 MVCC (快照读) + Next-Key Locking (当前读) 的组合,在绝大多数场景下解决了幻读问题

  3. READ UNCOMMITTED (RU - 读未提交):

    • 不使用 MVCC 的快照读,总是读取最新的物理记录(可能未提交),所以存在脏读。InnoDB 实际实现中,RU 级别下 SELECT 语句不会生成 Read View,也不走 Undo Log 链回溯,直接读最新版本。

  4. SERIALIZABLE (串行化):

    • 不使用 MVCC 的快照读。InnoDB 会将普通的 SELECT 语句自动转换为 SELECT ... LOCK IN SHARE MODE,即加共享锁读。所有读写操作都严格串行化,通过锁机制保证最高隔离,性能最低。不使用 Read View。


MVCC的优势

  1. 高并发读:读操作(快照读)完全不加锁,不会被写操作阻塞,极大地提高了数据库的并发读取性能。这是MVCC最核心的优势。
  2. 非阻塞读:写操作在更新数据时(需要加锁)不会阻塞读操作(基于旧版本快照)。
  3. 降低死锁概率:减少了读操作需要获取锁的情况,从而降低了发生死锁的可能性。
  4. 实现特定隔离级别: 高效地实现了 READ COMMITTED 和 REPEATABLE READ 隔离级别的语义,特别是避免了脏读和不可重复读(对快照读而言)。
http://www.xdnf.cn/news/919369.html

相关文章:

  • 81 实战一:给root目录扩容
  • SDC命令详解:使用set_port_fanout_number命令进行约束
  • robot_lab train的整体逻辑
  • 判断一个或者多个软件是否安装,如果没有则自动安装
  • 使用 Ansible 在 Windows 服务器上安装 SSL 证书系列之二
  • 无法与IP建立连接,未能下载VSCode服务器
  • 前端高频面试题2:浏览器/计算机网络
  • 零基础在实践中学习网络安全-皮卡丘靶场(第十三期-php反序列化)
  • 【读论文】U-Net: Convolutional Networks for Biomedical Image Segmentation 卷积神经网络
  • 如何在Unity中实现点击一个按钮跳转到哔哩哔哩
  • 大模型在创伤性脑出血全周期预测与诊疗方案中的应用研究
  • python打卡day47
  • spring:继承接口FactoryBean获取bean实例
  • Vue速查手册
  • Unity | AmplifyShaderEditor插件基础(第五集:简易膨胀shader)
  • GOOUUU ESP32-S3-CAM 果云科技开发板开发指南(一)(超详细!)Vscode+espidf 通过摄像头拍摄照片并存取到SD卡中,文末附源码
  • SMC自修改代码一
  • JUC 串讲
  • redis分片集群架构
  • 部门档案在不同系统中的差异及整合思路
  • 【自然语言处理】大模型时代的数据标注(主动学习)
  • PostgreSQL 的扩展pageinspect
  • 【Java学习笔记】Math方法
  • Xilinx IP 解析之 Block Memory Generator v8.4 ——02-如何配置 IP(仅 Native 接口)
  • oracle 11g ADG备库报错ORA-00449 lgwr unexpectedly分析处理
  • AGV|无人叉车工业语音播报器|预警提示器LBE-LEX系列性能与接线说明
  • Python从Excel读取数据并生成图表的方法详解
  • Docker构建Vite项目内存溢出:从Heap Limit报错到完美解决的剖析
  • LINUX67 FTP 3客户服务系统;FTP 上传、下载测试调试
  • 14-Oracle 23ai Vector Search 向量索引和混合索引-实操