深入理解MySQL的MVCC机制:事务隔离与一致性快照读
深入理解MySQL的MVCC机制:事务隔离与一致性快照读
MVCC(多版本并发控制)是MySQL InnoDB引擎实现高并发事务处理的关键技术之一。本文将通过具体的例子和详细的步骤,解释MVCC的核心概念,包括undo日志链表、ReadView的生成和使用,以及不同隔离级别下的数据可见性规则。
undo 日志链表
在介绍undo日志时提到,当InnoDB引擎底层开启一个新事务时,会分配一个全局唯一的事务ID。这个事务ID会被写入undo日志,并存储到数据表记录簇拥索引的trx_id
隐藏列中。另一个隐藏列roll_pointer
指针会指向该记录上一个版本的undo日志,发生事务回滚时可以通过该指针找到要回滚到的版本。
假设当前小明账户余额为470元,分别在两个事务中执行更新操作:
SQL 语句执行序列 | 事务 A | 事务 B |
---|---|---|
1 | BEGIN; | |
2 | BEGIN; | |
3 | UPDATE wallets SET balance=balance-2000 WHERE id=1; | |
4 | UPDATE wallets SET balance=balance+5000 WHERE id=1; | |
5 | COMMIT; | |
6 | UPDATE wallets SET balance=balance+8000 WHERE id=1; | |
7 | COMMIT; |
事务A对应的操作是小明先花了20块钱,钱包又充值了50块钱,假设事务ID是1000;事务B对应的操作是小强给小明转账了80块钱,假设事务ID是1200。
绘制出wallets簇拥索引中这条记录和对应的undo日志,相应的结构如下所示:
可以看到,每次记录更新后,上一个版本的值就会被存放到undo日志中,并且将当前最新记录的roll_pointer
指针指向该undo日志。这样一来,所有的roll_pointer
指针串成一个链表,该链表被称作版本链,版本链的头节点就是当前记录最新版本的值。版本链本质上就是个undo日志链表。
ReadView
判断记录的可见性
ReadView实际上是一个系统中所有活跃事务的列表,主要包含以下组成部分:
m_ids
:在生成ReadView时当前系统中活跃的事务ID列表;min_trx_id
:在生成ReadView时当前系统中活跃的事务中最小的事务ID,也就是m_ids
中的最小值;max_trx_id
:在生成ReadView时系统中应该分配给下一个事务的ID值;creator_trx_id
:生成ReadView的事务对应的事务ID,也就是当前事务ID。
有了这个ReadView之后,在访问某条记录时,只需要按照以下步骤判断该记录的某个版本是否可见:
- 如果被访问版本的
trx_id
属性值与ReadView中的creator_trx_id
值相同,意味着当前事务在访问它自己修改过的记录,所以该版本记录可以被当前事务访问。 - 如果被访问版本的
trx_id
属性值小于ReadView中的min_trx_id
值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本记录可以被当前事务访问。 - 如果被访问版本的
trx_id
属性值大于或等于ReadView中的max_trx_id
值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本记录不可以被当前事务访问。 - 如果被访问版本的
trx_id
属性值在ReadView的min_trx_id
和max_trx_id
之间,那就需要判断一下trx_id
属性值是不是在m_ids
列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本记录可以被访问。 - 如果某个版本的记录对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上述步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。
ReadView的生成时机
对于READ COMMITTED和REPEATABLE READ两种隔离级别而言,最大的区别就是ReadView的生成时机不同:
- 在READ COMMITTED隔离级别下,每个SELECT语句开始时,都会重新将当前系统中的所有的活跃事务拷贝到一个列表生成ReadView。
- 在REPEATABLE READ隔离级别下,每个事务执行第一个SELECT语句时,会将当前系统中的所有的活跃事务拷贝到一个列表生成ReadView,后续所有的SELECT都是复用这个ReadView。
MVCC机制
所谓的MVCC(Multi-Version Concurrency Control,多版本并发控制)指的就是在使用READ COMMITTED和REPEATABLE READ这两种隔离级别的情况下,事务在执行普通SELECT操作时访问数据库记录的版本链的过程。这样一来,我们就可以不通过加锁,而是通过MVCC机制使得不同事务的读写操作可以并发执行,从而提升MySQL系统在并发场景下的吞吐性能。
在不同的隔离级别下,MySQL通过MVCC让事务之间的并行操作遵循了某种规则,从而保证单个事务内前后数据的一致性。这个规则就是当前事务的查询可以看到自己之前所有已提交的事务所做的更改,而看不到未提交的事务所做的更改或者在查询开始之后提交的事务所做的更改,这种基于时间点的查询快照也被称作一致性快照读。
在MySQL InnoDB引擎中,只有READ COMMITTED和REPEATABLE READ这两种隔离级别才可以使用MVCC,应对高并发事务,MVCC比单纯的加行锁更有效,开销更小。