MVCC(Multi-Version Concurrency Control,多版本并发控制)
MVCC(Multi-Version Concurrency Control,多版本并发控制) 是数据库系统中的一种并发控制机制,用来处理多个事务同时操作数据库时可能产生的冲突问题。通过 MVCC,数据库在处理并发事务时,可以在提供高并发性的同时保证事务的隔离性(特别是在 MySQL 的 InnoDB 存储引擎中,默认的隔离级别是 可重复读)。
MVCC 的核心思想
MVCC 的核心思想是:为每个事务提供数据的不同版本,允许多个事务并发地读取和写入数据库,而不会互相阻塞。当一个事务在读取数据时,它不会立即看到其他事务正在写入的数据,而是读取数据的历史版本,这样避免了直接的锁冲突。
基本原理
MVCC 通过保存数据的多个版本来管理并发,事务在读取数据时可以选择读取到某个特定的版本,而不是直接读取当前最新的数据版本。InnoDB 使用了 Undo Log(回滚日志) 和事务版本控制来实现 MVCC。这里是 MVCC 的几个关键要素:
数据的隐藏版本(快照):
- 每条记录在数据库中都保存着多个版本的历史快照。
- 每个版本的数据都带有两个隐藏的时间戳字段:
trx_id
和roll_pointer
。trx_id
:记录是哪一个事务修改的该数据。roll_pointer
:指向 Undo Log,即这个版本的前一个版本。
Undo Log(回滚日志):
- 当一个事务修改数据时,InnoDB 会把修改前的旧数据保存到 Undo Log 中,这样可以实现数据回滚或提供历史版本数据给其他事务读取。
- Undo Log 会将旧的版本链起来,形成一个数据的版本链,每个事务可以根据自己的时间戳选择合适的版本。
事务的快照版本:
- 每个事务在开始时都会记录一个当前数据库系统的“快照”(系统中各条记录的状态)。
- 在事务的生命周期内,即使其他事务修改了数据,当前事务依然会读取到启动时看到的快照数据,而不会受到其他事务的影响。
事务时间戳:
- 每个事务都有一个唯一的时间戳,通常由事务启动的时间或者事务 ID 标识。这个时间戳帮助事务确定它可以看到的数据版本。
MVCC 读取与写入机制
1. 读取(读操作)
当一个事务读取数据时,MVCC 会选择一个特定版本的记录给这个事务,而不是直接返回最新的版本。这有两种主要的读取模式:
快照读(Snapshot Read):读取的是符合当前事务可见性的历史版本数据,事务并不会被其他事务阻塞。例如
SELECT
操作。- 对于可重复读隔离级别,事务在第一次读取数据时会创建一个快照,后续的读取操作都会基于这个快照,即使其他事务修改了数据,当前事务也不会感知到。
当前读(Current Read):直接读取最新版本的数据,并加锁以防止其他事务修改,例如
SELECT ... FOR UPDATE
、UPDATE
和DELETE
。当前读确保事务可以读到最新的数据,并且保证后续的操作都是基于最新的数据进行的。
2. 写入(写操作)
写入操作时,事务会:
- 生成一个新的版本,包含修改后的数据。
- 将修改前的旧版本写入 Undo Log,以便其他事务能够继续访问这个旧版本的数据。
- 为新版本数据打上当前事务的时间戳(
trx_id
),这样其他事务就知道哪个事务修改了这条数据。
在写入时,写操作的事务不会直接覆盖旧的数据,而是创建一个新的版本,因此不会阻塞正在进行的读操作。旧版本的数据依然可以被其他未提交的事务读取。
MVCC 的事务可见性规则
事务是否可以看到一条记录的某个版本取决于以下几条规则:
版本的
trx_id
小于当前事务的 ID:即当前事务开始之前,数据的这个版本已经提交了,因此这个版本是可见的。版本的
trx_id
大于当前事务的 ID:表示该版本是在当前事务启动之后由其他事务生成的,因此这个版本对当前事务是不可见的。删除的版本不可见:如果某个版本的数据在当前事务之前已经被其他事务删除,则当前事务也看不到这个被删除的版本。
举个简单的例子来解释 MVCC 的运作
假设有两个事务 A 和 B,它们都要对数据库中的某条记录进行操作。
事务 A:
sql START TRANSACTION; UPDATE orders SET amount = 500 WHERE id = 1;
事务 A 更新了订单 ID 为 1 的记录,将其金额修改为 500。但事务 A 并没有立即提交,这时另一个事务 B 开始读取数据。
事务 B:
sql START TRANSACTION; SELECT amount FROM orders WHERE id = 1;
根据 MVCC 机制,事务 B 看到的不是事务 A 修改后的数据(500),而是事务 A 修改前的旧数据(假设修改前是 400)。这就是 MVCC 通过读取数据的旧版本,避免了事务 B 被事务 A 的操作所阻塞。
事务 A 提交:
sql COMMIT;
当事务 A 提交后,事务 B 在它的事务结束前依然会看到旧的 400 数据,只有当事务 B 结束并重新开始新的事务时,才会读取到最新的 500 数据。
MVCC 优势与不足
优势:
- 高并发:MVCC 允许事务并发执行,读写操作不会互相阻塞,提升了系统的吞吐量。
- 读取历史版本:事务在读取数据时,能读取到符合自己事务版本的历史快照,确保事务的隔离性。
- 减少锁竞争:MVCC 在大部分情况下不需要加锁,避免了读写之间的直接冲突。
不足:
- 版本管理开销:MVCC 需要管理和维护多个数据版本,尤其是在长事务和大数据量场景下,回滚日志会增加存储和性能开销。
- 空间消耗:保存多个版本的历史数据需要额外的存储空间,Undo Log 也会占用存储资源。
- 清理旧版本的成本:数据库需要定期清理无用的旧版本数据,否则可能导致性能下降。
总结
MVCC 是 MySQL InnoDB 存储引擎用来处理并发问题的重要机制,它通过为每条数据维护多个版本,避免了读写之间的锁定冲突,提升了并发性能。事务在读取数据时读取的是符合其版本的快照,确保了隔离性,并且允许多个事务并发读写数据,保持数据一致性。