MVCC(多版本并发控制)介绍及实现原理

一、什么是MVCC?

MVCC(Multi-Version Concurrency Control,多版本并发控制)是数据库中用于解决并发访问问题的一种机制。它通过为数据维护多个版本,让读写操作在不同版本上独立进行,从而避免了传统锁机制中读写冲突导致的性能损耗(如读操作不会阻塞写操作,写操作也不会阻塞读操作)。

MVCC的核心目标是:在保证事务隔离性的前提下,提高数据库的并发性能。它广泛应用于支持行级锁的数据库(如MySQL的InnoDB、PostgreSQL等),尤其在读已提交(Read Committed) 和可重复读(Repeatable Read) 隔离级别中发挥关键作用。

二、MVCC的实现原理

MVCC的实现依赖于三个核心组件:数据的隐藏列 、回滚日志(Undo Log) 和ReadView(读视图)。三者协同工作,使得事务能够访问到符合其隔离级别的数据版本。

1. 数据的隐藏列

InnoDB存储引擎会为表中的每一行数据添加三个隐藏列,用于记录版本信息和事务相关数据:

  • DB_TRX_ID :记录最后一次修改该行数据的事务ID(6字节)。每次事务对行数据执行INSERT/UPDATE/DELETE操作时,都会将当前事务的ID写入该列。
  • DB_ROLL_PTR:回滚指针(7字节),指向该行数据的回滚日志(Undo Log),通过该指针可以找到数据的上一个版本。
  • DB_ROW_ID:行ID(6字节),当表没有主键或唯一索引时,InnoDB会用该列生成聚簇索引,确保每行数据有唯一标识。
2. 回滚日志(Undo Log)

回滚日志是MVCC实现多版本的基础,用于保存数据被修改前的旧版本。

  • 作用 :
    • 当事务需要回滚时,通过Undo Log恢复数据到修改前的状态(支持事务的原子性);
    • 为MVCC提供数据的历史版本,供其他事务读取(支持并发读写)。
  • 生成时机 :
    当事务执行INSERT/UPDATE/DELETE时,InnoDB会先将数据的旧版本写入Undo Log,再修改实际数据。
    • INSERT:Undo Log记录新插入的行信息,事务回滚时直接删除该行;
    • UPDATE/DELETE:Undo Log记录修改前的行数据,事务回滚时通过回滚指针恢复旧版本。
  • 版本链 :
    多次修改同一行数据时,Undo Log会形成一条"版本链":每次修改后,新数据的DB_ROLL_PTR指向旧版本的Undo Log,旧版本的DB_ROLL_PTR再指向更早的版本,直至最初版本。

回滚日志为什么在update和delete时不会被立即删除,而insert之后可以立即删除?

update和delete之后还会被mvcc或者是快照读用到**,这里举个mvcc还需要回滚日志的例子**

MVCC(多版本并发控制)通过回滚日志来实现数据多版本管理,以解决并发事务中的读 - 写冲突等问题,在可重复读隔离级别下体现得较为明显。以下是一个基于MySQL的InnoDB存储引擎的例子:

  1. 假设当前有三个事务,事务ID分别为100、200、300。
  2. 事务200先执行了一条SQL语句:UPDATE user SET name = '小王3号' WHERE id = 1,此时数据库会将修改前的记录相关信息写入回滚日志,然后修改实际数据,并将数据的trx_id更新为200。
  3. 接着事务200提交。
  4. 之后事务100开始执行查询语句,此时会生成一个ReadView,视图数组为(100, 300),min_id为100,max_id为300。
  5. 然后事务300执行了一条SQL语句:UPDATE user SET name = '小王4号' WHERE id = 1,数据库同样会将修改前的记录(即name = '小王3号')写入回滚日志,再修改实际数据,将trx_id更新为300。
  6. 此时事务100再次执行查询语句,根据MVCC的规则:
    • 若row的trx_id落于min_id和max_id之间,且不在视图数组中,说明这个版本是已经提交的事务生成的,是可见的。
    • 事务300的trx_id为300,在min_id和max_id之间,但在视图数组中,所以其修改后的结果对事务100不可见。
    • 事务200的trx_id为200,也在min_id和max_id之间,不在视图数组中,所以事务100会根据回滚日志找到事务200修改前的记录,查询结果为name = '小王3号'。

通过这个例子可以看到,MVCC利用回滚日志构建数据的旧版本,配合ReadView机制,让事务100在事务300已修改数据并提交的情况下,仍然能查询到符合可重复读规则的结果,体现了回滚日志在MVCC中的重要作用。

3. ReadView(读视图)

ReadView是事务在读取数据时生成的一个"快照",用于判断当前事务能看到哪些版本的数据。它本质上是一组用于过滤数据版本的规则,包含四个核心参数:

  • m_ids:当前活跃(未提交)的事务ID列表。
  • min_trx_id:活跃事务中最小的事务ID。
  • max_trx_id:系统为下一个事务分配的ID(即当前最大事务ID+1)。
  • creator_trx_id:生成该ReadView的事务ID。
4. 版本可见性判断规则

事务读取数据时,会根据ReadView的参数,对数据的DB_TRX_ID(最后修改事务ID)进行判断,决定是否可见:

  1. 若DB_TRX_ID == creator_trx_id:数据是当前事务自己修改的,可见。
  2. 若DB_TRX_ID < min_trx_id:修改该数据的事务在当前事务启动前已提交,可见。
  3. 若DB_TRX_ID > max_trx_id:修改该数据的事务在当前事务启动后才开始,不可见(需通过回滚指针找更早版本)。
  4. 若min_trx_id ≤ DB_TRX_ID ≤ max_trx_id:
    • 若DB_TRX_ID在m_ids中(事务仍活跃):不可见(需找更早版本);
    • 若DB_TRX_ID不在m_ids中(事务已提交):可见。

如果当前版本不可见,事务会通过DB_ROLL_PTR沿着Undo Log的版本链向上查找,直到找到符合规则的可见版本。

三、不同隔离级别下的MVCC行为

MVCC的具体表现与事务隔离级别相关,主要差异在于ReadView的生成时机:

  • 读已提交(Read Committed) :
    每次执行SELECT时都会重新生成ReadView。因此,事务在两次查询之间若有其他事务提交,可能会看到新提交的数据("不可重复读")。
  • 可重复读(Repeatable Read) :
    仅在事务第一次执行SELECT时生成ReadView,之后的查询复用该ReadView。因此,事务在整个生命周期中看到的数据版本是一致的("可重复读")。
四、MVCC的优势
  1. 读写不冲突:读操作通过访问旧版本数据,无需等待写操作释放锁;写操作仅锁定当前版本,不影响读操作。
  2. 隔离级别灵活:通过ReadView的生成时机和版本判断规则,适配不同隔离级别(读已提交、可重复读)。
  3. 支持事务回滚:结合Undo Log,确保事务失败时数据可恢复(原子性)。
总结

MVCC通过"隐藏列记录版本"、"Undo Log维护历史版本链"、"ReadView过滤可见版本"三者的配合,实现了多版本数据的并发访问。它既避免了传统锁机制的性能瓶颈,又保证了事务的隔离性,是现代数据库高效处理并发的核心技术之一。

相关推荐
weixin_460443561 小时前
2026年新修订《网络安全法》实施:Java私有化考试系统如何做好SBOM、漏洞治理与安全升级?
java·开发语言·在线考试系统·企业培训考试系统·宏远培训考试系统·国企培训考试系统·国产化培训系统
海鸥-w4 小时前
什么是AOP
java
仍然.5 小时前
Redis---缓存
数据库·redis·缓存
Cc.Y5 小时前
Java零基础入门:可变字符串与包装类:StringBuilder、StringBuffer 与 通讯录管理系统实战
java·开发语言·python
Aloudata5 小时前
LookML 语义模型 vs 企业级独立语义层:BI 建模语言能否承担企业语义底座?
数据库·人工智能·数据分析·数据资产·dataagent
paopaokaka_luck5 小时前
基于springboot+vue的智慧家政管理系统(AI问答、 WebSocket实时沟通、ECharts运营分析、订单规则与服务追踪)
java·javascript·spring boot·数据分析·echarts·mybatis
细嗅蔷薇@5 小时前
排序和分⻚(order by 、limit)
数据库·oracle
sm_926787055 小时前
RFID 标签打印的技术实现要点与二次开发实践
java·大数据·前端·c++·编辑器
阿森_dev5 小时前
2027 届毕设选题怎么选:30 个 Java 方向选题的实现难度排序
java·开发语言·spring boot·毕业设计·课程设计
Sarvartha5 小时前
Lambda 表达式和函数式接口
java·开发语言