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_idmax_id之间,且不在视图数组中,说明这个版本是已经提交的事务生成的,是可见的。
    • 事务300的trx_id为300,在min_idmax_id之间,但在视图数组中,所以其修改后的结果对事务100不可见。
    • 事务200的trx_id为200,也在min_idmax_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_IDm_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过滤可见版本"三者的配合,实现了多版本数据的并发访问。它既避免了传统锁机制的性能瓶颈,又保证了事务的隔离性,是现代数据库高效处理并发的核心技术之一。

相关推荐
清水白石00812 分钟前
解构异步编程的两种哲学:从 asyncio 到 Trio,理解 Nursery 的魔力
运维·服务器·数据库·python
资生算法程序员_畅想家_剑魔14 分钟前
Mysql常见报错解决分享-01-Invalid escape character in string.
数据库·mysql
一嘴一个橘子20 分钟前
spring-aop 的 基础使用 - 4 - 环绕通知 @Around
java
小毅&Nora36 分钟前
【Java线程安全实战】⑨ CompletableFuture的高级用法:从基础到高阶,结合虚拟线程
java·线程安全·虚拟线程
冰冰菜的扣jio37 分钟前
Redis缓存中三大问题——穿透、击穿、雪崩
java·redis·缓存
PyHaVolask40 分钟前
SQL注入漏洞原理
数据库·sql
小璐猪头1 小时前
专为 Spring Boot 设计的 Elasticsearch 日志收集 Starter
java
ptc学习者1 小时前
黑格尔时代后崩解的辩证法
数据库
代码游侠1 小时前
应用——智能配电箱监控系统
linux·服务器·数据库·笔记·算法·sqlite
ps酷教程1 小时前
HttpPostRequestDecoder源码浅析
java·http·netty