MVCC及其原理

1. MVCC概述及其原理

多版本并发控制(MVCC,Multi-Version Concurrency Control)是一种数据库管理技术,用于提高数据库系统在多用户环境中的并发性能,同时保证事务的隔离性,避免了不必要的锁定。MVCC允许在不同的事务中读取数据的早期版本,从而使读操作不会阻塞写操作,反之亦然。这种机制在很多数据库系统中都有实现,包括PostgreSQL和MySQL的InnoDB存储引擎。

MVCC原理

MVCC的核心思想是为数据库中的每一行数据保持不同版本的记录。这意味着当用户对数据库进行写操作(如更新或删除)时,系统会创建一行数据的新版本,而不是直接在原有数据上修改。每个版本的数据都有一个时间戳(或其他形式的版本标识),标识它被创建或修改的时间点。

当一个事务请求读取数据时,MVCC系统会返回该事务开始时刻可见的数据版本。具体来说,系统会根据以下规则来确定哪个版本的数据对当前事务是"可见"的:

  1. 创建版本号:每个数据版本在创建时都会被赋予一个唯一的版本号,这通常是事务的ID。这个版本号标识了数据被创建或修改的逻辑时间点。

  2. 删除版本号:当数据被另一个事务更新或删除时,原有版本的数据会被赋予一个删除版本号,也是事务的ID。这个版本号标识了数据停止被可见的逻辑时间点。

  3. 版本可见性规则:给定一个事务,如果某个数据版本的创建版本号小于或等于该事务的ID,且该数据版本没有被删除(即没有删除版本号)或其删除版本号大于该事务的ID,那么这个数据版本对该事务是可见的。

MVCC的优势

  • 提高并发性:读操作不会阻塞写操作,写操作也不会阻塞读操作,这大大提高了数据库的并发性能。
  • 减少锁的需求:由于读操作可以访问数据的早期版本,因此减少了对读写锁的需求,进一步提高并发性。
  • 事务隔离级别的支持:MVCC可以非常灵活地支持不同的事务隔离级别,包括读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。

MVCC的实现

不同的数据库系统实现MVCC的具体机制可能有所不同,但基本原理相似。以MySQL的InnoDB存储引擎为例,它使用以下机制来实现MVCC:

  • Undo日志:当数据被修改时,原始数据会被存储在Undo日志中。这允许系统在需要时构造出数据的早期版本。
  • Read View:当事务开始时,InnoDB会为该事务创建一个"读视图",决定哪些版本的数据对该事务可见。
  • 隐藏的系统列:InnoDB在每行数据中存储两个隐藏的系统列,记录了行的创建版本和删除版本,用于支持MVCC。

通过这种机制,MVCC能够在保证事务隔离性的同时,提高数据库的并发访问性能。

2. MySQL中的MVCC

在MySQL中,多版本并发控制(MVCC)主要通过InnoDB存储引擎实现。InnoDB使用一系列内部机制来支持MVCC,允许数据库在保持高并发的同时,确保数据的一致性和事务的隔离级别。这里是InnoDB实现MVCC的几个关键组成部分:

1. 隐藏列

InnoDB对每一行数据添加了三个隐藏的列来支持MVCC:

  • DB_TRX_ID:每当一行数据被修改时,InnoDB都会在这个隐藏列中存储修改该行的事务ID。
  • DB_ROLL_PTR:这个指针指向undo log记录,如果这行数据被多次修改,这些undo log记录形成一个链表。通过这个链表,InnoDB可以找到某个特定版本的行数据。
  • DB_ROW_ID:如果表没有定义主键,InnoDB会使用这个隐藏的行ID作为主键。

2. Undo日志

当事务更新数据时,InnoDB会将原始数据的副本存储在undo log中。这允许InnoDB在需要时回滚事务或重建旧的数据版本。对于MVCC来说,undo log使得读取事务能够看到事务开始之前的数据状态,即使这些数据后来被其他事务修改了。

3. Read View

当事务读取数据时,InnoDB会为该事务创建一个read view,这个视图定义了事务可以"看到"哪些行的版本。read view基于以下几个列表来判断数据行的可见性:

  • 活跃事务列表:在read view创建时,系统中所有活跃事务的ID列表。任何具有更高事务ID的数据修改都对当前事务不可见。
  • 上一个事务ID:创建read view时的最大事务ID。这帮助确定哪些数据版本是由尚未提交的事务创建的,因此对当前事务不可见。

4. 数据行的多个版本

通过以上机制,InnoDB能够为每个事务维护数据行的多个版本。当事务需要读取数据时,InnoDB会使用read view来决定哪个版本的数据对该事务是可见的。这取决于数据版本的创建事务ID与read view中活跃事务列表的比较。

如何工作

  • 当事务A更新一行数据时,InnoDB会将该行的当前版本写入undo log,并更新该行的DB_TRX_ID。
  • 如果此时事务B要读取相同的数据,InnoDB会检查事务B的read view。如果事务A的ID不在事务B的活跃事务列表中,这意味着事务A在事务B开始之前就已经提交,因此事务B可以看到事务A对该行所做的更改。
  • 如果事务A还未提交,事务B将看不到这些更改。InnoDB会使用undo log来为事务B提供该行数据的一个早期版本。

通过这种方式,InnoDB的MVCC机制支持了读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)等不同的事务隔离级别,并且允许并发事务高效安全地访问数据库,而无需对读操作加锁。

3. 例子

让我们通过一个简单的例子来说明InnoDB是如何实现MVCC的。假设我们有一个简单的银行账户表accounts,其中包含两列:account_id(账户ID)和balance(余额)。现在,我们有两个事务同时操作这个表:事务A(Tx A)要更新一个账户的余额,而事务B(Tx B)想要读取一些账户的余额信息。

初始状态

  • accounts表中有一条记录:account_id = 1, balance = 100

事务A的操作

  1. 开始事务A :事务A开始,并打算将account_id = 1的账户余额更新为200。
  2. 更新操作 :InnoDB在更新余额之前,会将这行数据当前的版本(balance = 100)存储到undo log中,并将这行数据的DB_TRX_ID更新为事务A的事务ID。
  3. 余额更新account_id = 1的账户余额现在变为200。

事务B的操作

  1. 开始事务B:事务B开始,想要读取所有账户的余额信息。
  2. 构建Read View:为事务B创建一个read view,这个read view包含了事务开始时系统中所有未完成的事务ID。由于事务A尚未提交,它的ID也在这个列表中。

读取操作

  1. 事务B读取account_id = 1的余额 :当事务B尝试读取这个账户的余额时,InnoDB检查这行数据的DB_TRX_ID
  2. 使用Read View判断数据版本的可见性:由于事务A的ID在事务B的read view活跃事务列表中,这意味着事务B不能看到事务A所做的更改。
  3. 通过Undo Log访问旧数据 :InnoDB使用undo log中的信息,提供给事务B这行数据的旧版本(balance = 100),即使当前表中的数据已经被更新为200。

事务提交

  1. 事务A提交 :事务A完成更新操作后提交。此时,InnoDB会更新这行数据的DB_TRX_ID,表示这个版本的数据是由事务A创建的。
  2. 事务B读取操作之后:即使事务A已经提交,由于事务B的read view是在事务B开始时创建的,事务B仍然只能看到旧版本的数据。

结论

通过这个例子,我们可以看到InnoDB的MVCC是如何工作的:

  • Undo Log:保留数据的旧版本,使得即使数据被更新,旧的事务也可以看到更新前的数据。
  • Read View:定义了一个事务可以看到哪些数据版本,确保事务的隔离性。
  • DB_TRX_ID:标识了数据版本是由哪个事务创建的,用于决定数据的可见性。

这种机制允许事务B在事务A更新数据的同时读取数据,而不会看到未提交的更改,从而实现了非锁定读取和高并发性。

相关推荐
天天扭码17 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶18 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺22 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序30 分钟前
vue3 封装request请求
java·前端·typescript·vue
gma99938 分钟前
Etcd 框架
数据库·etcd
爱吃青椒不爱吃西红柿‍️41 分钟前
华为ASP与CSP是什么?
服务器·前端·数据库
陈王卜1 小时前
django+boostrap实现发布博客权限控制
java·前端·django
小码的头发丝、1 小时前
Spring Boot 注解
java·spring boot
java亮小白19971 小时前
Spring循环依赖如何解决的?
java·后端·spring
飞滕人生TYF1 小时前
java Queue 详解
java·队列