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

[MySQL] 事务的隔离性与 MVCC - 详解

MySQL 事务与 MVCC

1. 事务的隔离性

在这里插入图片描述

在没有任何隔离的情况下,“仅读事务” 与 “读写事务” 的并发执行可能会出现下面的问题:

​ v2 = 2:

我可以读到未提交事务中进行的修改,所谓的 “读未提交”。就是​ 如果你认为事务 B 还没有提交,它的修改就不能算,这个结果就是错误的。如果你认为,按照时间线来说,这个值是符合预期的,这个结果就没难题。此种情况叫 “脏读”,也就

​ v1 != v2:

​ 在一个事务中你读到了一个字段的不同值。此种情况叫 “不可重复读”,因为不保证你在事务中每次读取数据都是一致的。

​ 除此之外还有一种情况,就是如果我们的 WHERE 条件是一个范围查询,那么其他事务可能会同时进行一些插入操作,这样会导致返回的行数不一致。此种情况叫 “幻读”,其实你也可以将它理解为不可重复读。

如何解决这些问题的:就是下面我们来看看 InnoDB

​ 假如想解决 “脏读” 问题,就要保证:只要事务没提交,它的改变就不对其他事务可见。InnoDB 通过在每个 SQL 语句开始执行的时候创建一个 “视图” 来保证这点,这个视图保证,这条 SQL 语句只能看见已提交事务的修改。此时事务的隔离级别为 “读提交”。

​ 如果想解决 “不可重复读” 和 “幻读” 问题,就要保证:从事务开启到提交这个过程中,它不会看到任何内容的改变。InnoDB 通过在每个事务开始执行的时候(事务的第一条 SQL 语句)创建一个视图来保证这点,也就是这个事务中全部的 SQL 语句共用一个视图。此时事务的隔离级别为 “可重复读”。

2. 隔离性实现原理

​ 首先先来说一下刚刚反复提到的 “视图” 到底是一个什么东西,在 InnoDB 源码中它其实是一个这样的 C++ 数据结构:

class ReadView {/* ... */
private:trx_id_t m_low_limit_id;      /* 下一个待分配的事务 ID */trx_id_t m_up_limit_id;       /* 最小的未提交事务 ID */trx_id_t m_creator_trx_id;    /* 当前事务自己的 ID */trx_id_t m_low_limit_no;      /* 下一个待分配的事务 no */ids_t m_ids;                  /* 创建 Read View 时的未提交事务 ID 列表 */m_closed;                     /* 标记 Read View 是否 close */
}

在这里插入图片描述
​ InnoDB 为了使 “仅读事务” 与 “读写事务” 并发执行,会为数据库中的每条记录维护多个版本,这些版本实际上就是对该记录的每一次修改,它们构成了一个版本链。InnoDB 会为聚簇索引添加两个隐藏列用于维护这个版本链,分别是:

trx_id:对该记录改动的 SQL 所属的事务 ID。

roll_pointer:使用这个指针将一条记录的众多版本串成一个时间上的有序链表,实际上就是指向 Undo Log 的指针。

​ InnoDB 中当前数据页上存储的是最新版本的数据,旧版本的内容通过 Undo Log 构建。因此版本链直观来看是类似下面的数据结构:

以事务为维度的,每个事务都会被分配自己的 Undo Log 组。就是​ 但注意,图上所示是 Undo Log 的逻辑结构,不是物理结构。实际上 Undo Log 的真正硬盘存储

​ 介绍了 “版本链” 这个概念之后,就可以明确 Read View 的作用了:判断版本链中的哪个版本对当前事务可见。当我们执行一条 SELECT 时,InnoDB 会顺序遍历对应记录版本链中的 trx_id,对每个 trx_id 做如下判断:

​ 如果 trx_id 小于 Read View 中的 m_up_limit_id,说明这个版本一定属于已提交事务,因此可见。

​ 如果 trx_idm_up_limit_idm_low_limit_id 之间,那就要看 trx_id 是否在 m_ids 中可以找到, 如果可以找到,说明这个版本属于未提交事务,因此不可见。如果找不到,说明其依然属于已提交事务,因此可见。并且,我们知道 m_creator_trx_id 是一定位于这个范围的,如果 trx_id 等于 m_creator_trx_id,说明这个版本是当前事务创建的,因此可见。

​ 如果 trx_id 大于等于 m_low_limit_id,说明这个版本所属事务的开启是晚于当前事务的,因此不可见。

​ 这个循环一直进行,直到找到第一个可见版本,呈现给当前事务。这就是所谓的 MVCC,也就是在 “读提交” 和 “可重复读” 这两种隔离级别的事务执行 SELECT 操作时,访问记录版本链的过程。它的存在就是为了使 “仅读事务” 和 “读写事务” 可以安全地并发执行而无需加锁控制。

3.purge

​ 现在还有一个问题,就是这个版本链越来越长,什么时候清理呢?我们先来想一下,Undo Log 有两个作用,一个是事务回滚,一个是 MVCC。从事务回滚的角度来说,当事务提交时,这个事务产生的全部 Undo Log 就都没用了,因为不存在回滚了,这个时候删除即可,只是由于 Undo Log 还要服务 MVCC,故而这个删除时机是不对的,正确的删除时机还要更艰难一些:

​ 对于 INSERT 语句,它产生的 Undo Log 只用于回滚,也就是 DELETE,但是不需要服务 MVCC。为什么呢?因为插入是一个从无到有的过程,它没有所谓旧版本这一说。其他事务要么能 SELECT 出这一行,要么 SELECT 不出,没有版本选择问题。

​ 对于 UPDATE 语句产生的 Undo Log,InnoDB 是这样处理的:

​ 我们知道 Undo Log 是以事务为单位进行组织的,每个事务产生的众多 Undo Log 页面链接在一起。事务提交时,这组 Update Undo Log 会被转移到其所在回滚段的 History 链表中,这个链表就是为了支持 MVCC 而设计的,它会保留直至不被任何 Read View 需要为止。那么什么时候一条 Undo Log 才不被任何 Read View 需要呢?我们需要从下面两点来考虑:

1. 如果一个事务已提交,那么它对数据的修改一定对其提交之后产生的 Read View 可见

2. 对整个系统现存最早生成的 Read View 可见的数据版本,一定对其他 Read View 都可见。

​ 因此,假设一组 Undo Log 对应的事务提交是早于 Oldest Read View 的生成的,那么这组 Undo Log 就肯定没用了,这就是 InnoDB 的 purge 逻辑。

​ 具体来说,InnoDB 是通过在每组 Undo Log 的 Header 中的 TRX_UNDO_TRX_NO 属性,与 Read View 中的 m_low_limit_no 属性来控制的。生成 Read View 的时候,m_low_limit_no 会被赋为当前系统中最大的 no + 1 的值。事务提交时,当前事务生成自己的 no,这个 no 是严格以事务提交为顺序的,提交越早的事务 no 被分配得越小,反之则越大,然后将这个值赋给 TRX_UNDO_TRX_NO

​ 可能听起来有点乱,可是只要记住这个 no 就是用来记录事务提交与 Read View 被创建之间的相对时间先后的就好,这与刚刚的第一点是呼应的。如果还是没理解可以看下面的图片:

在这里插入图片描述
​ 这提醒了我们,影响 Undo Log 清理的是最老的 Read View,所以一个可重复读的长事务会严重拖慢 Undo Log 的清理效率。由于长事务很可能在使用最老的 Read View,那么任何比这个 Read View 的 no 值大的 Undo Log 都不能被清除。

4. 总结

​ MVCC 是用于保护一般 SELECT 语句进行 “快照读” 的机制,根本目的就是使这种 “仅读事务” 能够在不对表进行锁控制的情况下安全地与其他事务并发执行。

​ 对于 “读写事务” 和 “读写事务” 的并发来说,MVCC 就无能为力了,还是要进行一定的锁控制。

http://www.jsqmd.com/news/289774/

相关文章:

  • 【实战项目】 springboot作业管理系统
  • 5.IP地址详解
  • 查看SQL server的端口号
  • SQL Server 支持多种网络协议用于客户端与数据库引擎之间的通信
  • Java全栈开发面试实战:从基础到高阶的技术对话
  • 为什么现在人人都在谈 AI Agent?
  • 2026陕西西安灯杆加工厂家推荐:两大实力企业领跑激光切割赛道
  • 2026年全国果蔬粉哪家好?选型实用指南 聚焦功能性与场景适配 品牌差异化对比
  • 线缆拉力试验机供应商推荐:盘点从源头到经销商的核心企业
  • cmake 常用命令解析(工作总结持续更新中)
  • 【实战项目】 粒子群算法在数据挖掘中的应用研究
  • pgsql创建只读账号
  • 安徽地区小红书代运营全解析:芜湖优选三十六行网络科技破局增长
  • 2026年百度竞价广告开户推广代运营评测:昊客网络以核心运营策略脱颖而出。
  • 数字员工与AI销冠系统是什么?主要具备哪些提升商业效率的优势?
  • 【实战项目】 HTTP缓存机制在Web系统中的优化
  • 大模型还在“间歇性失忆“?DeepSeek这波操作直接把记忆焊死在模型里!小白程序员也能轻松上手的革命性技术
  • AI Agent28个高频面试问题与准备策略总结
  • 所谓 RAG,看这一篇就够了!
  • 从“调参侠“到“系统架构师“:这款自我进化的RAG系统正在改写AI应用的底层逻辑
  • 从入门到精通:6步搭建企业级RAG系统,让你的AI应用不再‘胡说八道‘
  • 实战 | 零基础搭建知识库问答机器人:基于SpringAI+RAG的完整实现
  • 打工人真实测评:2026适合办公室吃的健康零食品牌推荐!
  • AI Agent 三件套终章:它居然会“动手”?!——工具使用能力大揭秘
  • 全自动测油仪品牌有哪些?行业TOP2品牌厂家深度推荐
  • 枚举类型 enum:让常量更具语义化
  • C++ 指针入门:地址、指针变量与解引用操作
  • 鳍片方向之争:竖直 vs 水平,哪种散热更强?
  • 基于散热模组锁附应力与热应力的耦合分析
  • 2026芜湖抖音短视频运营TOP5热销榜策划公司,托管服务全面提升品牌曝光