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过滤可见版本"三者的配合,实现了多版本数据的并发访问。它既避免了传统锁机制的性能瓶颈,又保证了事务的隔离性,是现代数据库高效处理并发的核心技术之一。

相关推荐
深度学习04071 小时前
【Linux服务器】-MySQL数据库参数调优
linux·服务器·数据库
ZC1111K2 小时前
maven(配置)
java·maven
慕y2743 小时前
Java学习第五十八部分——设计模式
java·学习·设计模式
躲在云朵里`3 小时前
SpringBoot的介绍和项目搭建
java·spring boot·后端
别致的影分身3 小时前
MySQL InnoDB 存储引擎
数据库·mysql
菜还不练就废了3 小时前
7.19-7.20 Java基础 | File类 I/O流学习笔记
java·笔记·学习
Yweir3 小时前
Elastic Search 8.x 分片和常见性能优化
java·python·elasticsearch
设计师小聂!4 小时前
尚庭公寓--------登陆流程介绍以及功能代码
java·spring boot·maven·mybatis·idea
PythonicCC4 小时前
Django ORM系统
数据库·django
心平愈三千疾4 小时前
学习秒杀系统-页面优化技术
java·学习·面试