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

MVCC实现原理

​MVCC机制简明指南​

MVCC(Multi-Version Concurrency Control,多版本并发控制)是数据库实现高并发的核心技术,​​核心思想​​是通过保存数据的多个版本,让读写操作互不阻塞。以下是其核心原理和实现逻辑:


​一、MVCC解决什么问题?​

​1. 传统锁机制的痛点​

  • ​读-写冲突​​:读数据时加锁,会阻塞写操作(反之亦然)。
  • ​并发度低​​:悲观锁(如SELECT FOR UPDATE)导致大量线程等待。

​2. MVCC的解决方案​

  • ​读操作​​:读取数据的​​历史快照​​(无需加锁)。
  • ​写操作​​:创建新版本数据,不影响正在读取旧版本的事务。

​二、MVCC的实现原理(以InnoDB为例)​

​1. 隐藏字段​

每行记录包含两个隐藏字段:

  • DB_TRX_ID​:最后一次修改该行的事务ID。
  • DB_ROLL_PTR​:指向Undo Log中旧版本数据的指针(构成版本链)。
idnameageDB_TRX_IDDB_ROLL_PTR
1Alice251010x123456

​2. Undo Log(回滚日志)​

  • 存储数据被修改前的值,用于:
    • ​事务回滚​​:恢复到修改前的状态。
    • ​构建版本链​​:支持MVCC读取历史版本。

​3. ReadView(读视图)​

事务每在读取数据时生成一个快照,决定能看到哪些版本的数据:

  • m_ids​:当前活跃(未提交)的事务ID列表,是一个集合
  • min_trx_id​:所有正在并发的活跃事务中最小ID。
  • max_trx_id​:系统预分配的下一个事务ID。
  • creator_trx_id​:创建该ReadView的事务ID。

​三、MVCC的工作流程​

​1. 读操作(SELECT)​

  • m_ids​:当前活跃(未提交)的事务ID列表,是一个集合
  • min_trx_id​:所有正在并发的活跃事务(​m_ids)中最小ID。
  • max_trx_id​:系统预分配的下一个事务ID
  • creator_trx_id​:创建该ReadView的事务ID(查询事务)。
  • DB_TRX_ID​:最后一次修改该行的事务ID。最新修改的事务ID。

判断redo log版本链中,到底那一条数据可以被当前事务看见:

  1. 检查行数据的DB_TRX_ID
    • 如果DB_TRX_ID < min_trx_id:说明该版本已提交,​​可见​​。
    • 如果DB_TRX_ID > max_trx_id:说明该版本是未来事务修改的,​​不可见​​。
    • 如果min_trx_id ≤ DB_TRX_ID ≤ max_trx_id
      • DB_TRX_IDm_ids中:说明事务未提交,​​不可见​​。
      • 否则:​​可见​​。
  2. 若不可见,则通过DB_ROLL_PTR找到Undo Log中的旧版本,重复判断。

​2. 写操作(UPDATE/INSERT/DELETE)​

  • ​UPDATE​​:拷贝当前行到Undo Log,修改当前行并更新DB_TRX_ID
  • ​DELETE​​:标记删除(逻辑删除),通过DB_ROLL_PTR保留旧版本。
  • ​INSERT​​:直接插入新行,分配新DB_TRX_ID

​四、MVCC如何保证隔离级别?​

​隔离级别​​MVCC的实现方式​
​读未提交(RU)​直接读最新数据,忽略版本链
​读已提交(RC)​每次SELECT生成新ReadView,能看到其他事务已提交的修改
​可重复读(RR)​事务内第一次SELECT生成ReadView,后续复用该视图(MySQL默认,避免不可重复读和幻读)
​串行化(S)​退化为悲观锁(如加表锁)

​五、MVCC的优缺点​

​优点​

  • ​读不阻塞写,写不阻塞读​​:大幅提升并发性能。
  • ​避免脏读和不可重复读​​:通过版本链和ReadView机制。

​缺点​

  • ​存储开销​​:需额外保存历史版本(Undo Log占用空间)。
  • ​清理成本​​:需要定期清理不再需要的旧版本(Purge线程)。

​六、示例说明​

假设事务A(ID=100)和事务B(ID=101)并发操作:

  1. ​初始数据​​:行X的DB_TRX_ID=90(已提交)。
  2. ​事务B修改行X​​:
    • 将旧值存入Undo Log,更新行X的DB_TRX_ID=101
  3. ​事务A读取行X​​:
    • 生成ReadView:m_ids=[101](事务B未提交),min_trx_id=101
    • 发现行X的DB_TRX_ID=101m_ids中,不可见 → 通过DB_ROLL_PTR读取Undo Log中的旧版本(DB_TRX_ID=90),返回该值。

​总结​

MVCC通过​​版本链+读视图​​的机制,在保证事务隔离性的同时,最大化并发性能。它是MySQL高并发能力的基石,理解其原理对优化SQL和排查并发问题至关重要。

​通俗易懂版解释:MVCC的可见性规则​

你可以把MVCC的可见性判断想象成一个"时间线游戏",通过比较事务ID的大小关系,决定当前事务能看到哪个版本的数据。下面用最直白的语言和例子解释:


​1. 关键角色说明​

  • DB_TRX_ID​:每行数据上贴的"最后修改者身份证号"(事务ID)。
  • min_trx_id​:当前系统中"最老未提交事务的身份证号"。
  • max_trx_id​:系统即将分配的下一个事务ID(未来事务的起点号)。
  • m_ids​:当前所有"未提交事务的身份证号列表"。

​2. 判断规则拆解​

​情况1:DB_TRX_ID < min_trx_id(已提交的旧数据)​
  • ​比喻​​:你(当前事务)在查资料时,发现这份资料的修改者(DB_TRX_ID=50)比教室里最老的学生(min_trx_id=100)还早,说明他早就交卷离开(已提交)。
  • ​结论​​:这份资料是​​可见的​​。
​情况2:DB_TRX_ID > max_trx_id(未来的数据)​
  • ​比喻​​:资料上写着修改者ID是200,但系统现在最大只分配到150,这个ID属于"未来人"。
  • ​结论​​:这份资料是​​不可见的​​(可能是系统异常)。
​情况3:min_trx_id ≤ DB_TRX_ID ≤ max_trx_id(可能是未提交的数据)​
  • ​子情况3.1:DB_TRX_IDm_ids列表中​

    • ​比喻​​:资料修改者(DB_TRX_ID=120)还在教室里考试(未提交事务列表中有他)。
    • ​结论​​:​​不可见​​(不能看他未提交的答案)。
  • ​子情况3.2:DB_TRX_ID不在m_ids列表中​

    • ​比喻​​:资料修改者ID是130,但教室里没这个人(说明他已交卷离开)。
    • ​结论​​:​​可见​​。
​如果不可见怎么办?​
  • ​动作​​:顺着DB_ROLL_PTR指针(类似"上一版本链接")找到旧版本数据,重新判断。
  • ​比喻​​:当前资料不可见,就去翻看它的历史修订版,直到找到符合条件的版本。

​3. 实际例子​

假设当前系统状态:

  • 活跃事务列表m_ids = [100, 110](事务100和110未提交)
  • min_trx_id = 100, max_trx_id = 120

某行数据的版本链:

版本3: DB_TRX_ID=110 (通过ROLL_PTR指向版本2)
版本2: DB_TRX_ID=100 (通过ROLL_PTR指向版本1) 
版本1: DB_TRX_ID=80

​事务A(ID=105)读取该行时的判断过程​​:

  1. 先看最新版本(版本3):
    • DB_TRX_ID=110min_trx_id=100 → 属于情况3
    • 110m_ids=[100,110]中 → 不可见
  2. 通过ROLL_PTR找到版本2:
    • DB_TRX_ID=100min_trx_id=100 → 属于情况3
    • 100m_ids中 → 不可见
  3. 继续找版本1:
    • DB_TRX_ID=8080 < min_trx_id=100 → 属于情况1
    • ​可见!​​ 最终返回版本1的数据。

​4. 为什么这样设计?​

  • ​读已提交(RC)​​:每次查询都重新生成ReadView,能看到其他事务最新提交的数据。
  • ​可重复读(RR)​​:事务内第一次查询生成ReadView后固定不变,保证多次读取结果一致。

​总结​

MVCC的可见性规则本质是:
​通过比较事务ID,判断数据版本是否由已提交的事务创建​​。

  • 比"最老未提交事务"更早的 → 安全可见
  • 属于"未提交事务"的 → 不可见
  • 属于"已提交但不算太老"的 → 也可见

就像考试时,你只能参考已交卷同学的答案,不能看还在答题的同学的试卷!

http://www.lqws.cn/news/144127.html

相关文章:

  • bug 记录 - 使用 el-dialog 的 before-close 的坑
  • 网站首页菜单两种布局vue+elementui顶部和左侧栏导航
  • Pandas和Django的示例Demo
  • wpf Behaviors库实现支持多选操作进行后台绑定数据的ListView
  • MySQL 高可用基石 - 复制监控与常见 HA 方案
  • DBSyncer:开源数据库同步利器,MySQL/Oracle/ES/SqlServer/PG/
  • 企业培训学习考试系统源码 ThinkPHP框架+Uniapp支持多终端适配部署
  • 行列式的性质
  • exp1_code
  • Redis 缓存策略:借助缓存优化数据库性能并保障数据一致性
  • 9.axios底层原理,和promise的对比(2)
  • 外网访问内网服务器常用的三种简单操作步骤方法,本地搭建网址轻松让公网连接
  • 游戏设计模式 - 子类沙箱
  • OCR助力保险业建设
  • 【C++】二叉搜索树
  • ocrapi服务docker镜像使用
  • 从零开始的云计算——番外实战,iptables防火墙项目
  • WordZero:让Markdown与Word文档自由转换的Golang利器
  • 【Go语言基础【2】】数据类型之基础数据类型:数字、字符、布尔、枚举、自定义
  • 1、Go语言基础中的基础
  • 【Go语言基础【四】】局部变量、全局变量、形式参数
  • PPT转图片拼贴工具 v3.0
  • Spark 写文件
  • Dubbo Logback 远程调用携带traceid
  • 41道Django高频题整理(附答案背诵版)
  • PostgreSQL 的扩展pg_prewarm
  • 20250605在微星X99主板中配置WIN10和ubuntu22.04.6双系统启动的引导设置
  • Django CMS 的 Demo
  • NoSQL之Redis配置与优化
  • SQL Server相关的sql语句