MySQL 的 InnoDB 存储引擎使用多版本并发控制(Multiversion Concurrency Control, MVCC)来提高数据库的并发性能,同时保持事务的隔离性。MVCC 通过为每个事务提供一个数据的一致性视图来实现这一点,而不需要在读取时加锁。以下是 MySQL InnoDB 中 MVCC 的实现原理分析:
基本概念
-
事务ID (Transaction ID, TX_ID):
- 每个事务开始时都会被分配一个唯一的事务ID。
- 事务ID是单调递增的,新事务的ID总是大于旧事务的ID。
-
隐藏列:
- InnoDB 为每一行数据添加了两个隐藏列:
DB_TRX_ID
和DB_ROLL_PTR
。DB_TRX_ID
:记录创建或最后修改该行的事务ID。DB_ROLL_PTR
:指向回滚段(Undo Log)中的记录,用于回滚操作。
- InnoDB 为每一行数据添加了两个隐藏列:
-
回滚段 (Undo Log):
- 回滚段存储了旧版本的数据,用于回滚事务和提供一致性读。
- 当事务修改数据时,旧版本的数据会被复制到回滚段中,并通过
DB_ROLL_PTR
指针链接起来。
-
Read View:
- 每个事务开始时会创建一个 Read View,它包含了当前活动事务的列表。
- Read View 用于确定哪些版本的数据对当前事务是可见的。
MVCC 实现原理
1. 插入操作
- 当插入一条新记录时,
DB_TRX_ID
被设置为当前事务的ID。 DB_ROLL_PTR
通常为空,因为没有旧版本的数据需要回滚。
2. 更新操作
- 当更新一条记录时,InnoDB 会将旧版本的数据复制到回滚段中,并通过
DB_ROLL_PTR
指针链接起来。 - 新版本的记录会被插入到表中,
DB_TRX_ID
设置为当前事务的ID。 - 旧版本的数据可以通过
DB_ROLL_PTR
指针找到,形成一个版本链。
3. 删除操作
- 当删除一条记录时,实际上是在记录上标记一个删除标志(逻辑删除),而不是物理删除。
DB_TRX_ID
设置为当前事务的ID。- 旧版本的数据同样被复制到回滚段中,并通过
DB_ROLL_PTR
指针链接起来。
4. 读取操作
- 读取操作根据当前事务的 Read View 来决定哪些版本的数据是可见的。
- 对于每一条记录,InnoDB 会检查
DB_TRX_ID
和 Read View 中的事务ID列表,以确定是否应该读取当前版本的数据还是从回滚段中读取旧版本的数据。
读取规则
-
不可重复读 (Repeatable Read) 隔离级别:
- 只能看到在事务开始之前已经提交的数据。
- 不可见在事务开始之后提交的数据。
- 如果
DB_TRX_ID
小于 Read View 中的最小事务ID,则数据可见。 - 如果
DB_TRX_ID
大于或等于 Read View 中的最大事务ID,则数据不可见。 - 如果
DB_TRX_ID
在 Read View 的事务ID列表中,则数据不可见。 - 否则,数据可见。
-
读已提交 (Read Committed) 隔离级别:
- 只能看到在当前查询开始之前已经提交的数据。
- 每次查询都会生成一个新的 Read View。
总结
MVCC 通过为每个事务提供一个数据的一致性视图来实现高并发下的事务隔离。主要机制包括:
- 为每一行数据添加隐藏列
DB_TRX_ID
和DB_ROLL_PTR
。 - 使用回滚段存储旧版本的数据。
- 每个事务开始时创建一个 Read View,用于确定哪些版本的数据是可见的。
- 读取操作根据 Read View 来决定读取哪个版本的数据。
通过这种方式,InnoDB 能够在不加锁的情况下支持高并发读写操作,从而提高了系统的整体性能。