InnoDB的MVCC实现原理?MVCC如何实现不同事务隔离级别?MVCC优缺点?
概念
InnoDB的MVCC(Multi-Version Concurrency Control)即多版本并发控制,是一种用于处理并发事务的机制。它通过保存数据在不同时间点的多个版本,让不同事务在同一时刻可以看到不同版本的数据,以此来减少锁竞争,提高数据库的并发性能,同时保证事务的隔离性。
实现原理
隐藏列
InnoDB会为表中的每行记录添加三个隐藏列:
- DB_TRX_ID:记录最后一次对该行记录进行插入或更新操作的事务ID。当进行删除操作时,也会将其视为一次更新操作,只是会将该行记录标记为已删除。
- DB_ROLL_PTR:回滚指针,指向该行记录的上一个版本所在的回滚段。通过这个指针,能找到该行记录的历史版本。
- DB_ROW_ID:如果表没有主键,InnoDB会自动生成一个隐藏的自增主键。
回滚段(Rollback Segment)
回滚段用于存储数据的旧版本。当一个事务对某行记录进行更新或删除操作时,InnoDB不会直接覆盖原数据,而是将旧版本的数据复制到回滚段中,并更新当前行的DB_TRX_ID
和DB_ROLL_PTR
。这样,就可以保留数据的历史版本,供其他事务查看。
事务ID
每个事务在启动时都会被分配一个唯一的事务ID,且这个ID是按照事务启动的顺序依次递增的。
可见性判断规则
在不同的事务隔离级别下,InnoDB根据事务的开始时间和记录的隐藏列信息来判断哪些版本的数据对当前事务是可见的。以下以可重复读(REPEATABLE READ)隔离级别为例说明:
- 当事务开始时,会获取一个当前系统中所有活跃事务ID的列表。
- 对于要查询的每一行记录:
- 如果该行记录的
DB_TRX_ID
小于当前事务开始时活跃事务ID列表中的最小值,说明该行记录是在当前事务开始之前就已经提交的事务修改的,当前事务可以看到这个版本的数据。 - 如果该行记录的
DB_TRX_ID
大于当前事务开始时活跃事务ID列表中的最大值,说明该行记录是在当前事务开始之后才开始的事务修改的,当前事务看不到这个版本的数据。 - 如果该行记录的
DB_TRX_ID
在当前事务开始时活跃事务ID列表的范围内,需要进一步判断该事务是否已经提交。如果已经提交,当前事务可以看到这个版本的数据;如果未提交,当前事务看不到这个版本的数据。
- 如果该行记录的
不同事务隔离级别下的MVCC表现
读未提交(READ UNCOMMITTED)
此隔离级别下,MVCC基本不起作用。事务可以读取到其他事务未提交的数据,不会考虑数据版本的可见性规则,可能会出现脏读问题。
读已提交(READ COMMITTED)
- 每次查询时都会生成一个新的快照,即获取当前最新的活跃事务ID列表。
- 事务只能看到已经提交的事务所做的更改,避免了脏读问题,但可能会出现不可重复读问题,因为在同一个事务中不同时间点的查询可能会看到不同版本的数据。
可重复读(REPEATABLE READ)
- 事务在开始时会生成一个快照,在整个事务期间都会使用这个快照。
- 同一个事务中多次读取相同的数据,看到的版本始终是一致的,避免了不可重复读问题,但可能会出现幻读问题(不过InnoDB通过间隙锁等机制在一定程度上解决了幻读问题)。
串行化(SERIALIZABLE)
该隔离级别下,MVCC不发挥作用。事务会对读取的数据加共享锁,对写入的数据加排他锁,事务之间是串行执行的,避免了所有并发问题,但会导致并发性能极低。
MVCC的优点
- 提高并发性能:多个事务可以同时对数据进行读写操作,减少了锁竞争,从而提高了数据库的并发处理能力。
- 实现事务隔离:不同的事务可以看到不同版本的数据,从而实现了不同级别的事务隔离,保证了数据的一致性和完整性。
- 避免死锁:由于减少了锁的使用,降低了事务之间相互等待锁的情况,减少了死锁的发生概率。
MVCC的局限性
- 占用额外存储空间:为了保存数据的多个版本,需要使用回滚段来存储旧版本的数据,这会占用额外的存储空间。
- 增加系统复杂度:MVCC的实现需要维护隐藏列、回滚段和事务ID等信息,增加了系统的复杂度和管理难度。
- 可能导致长事务问题:如果一个事务长时间不提交,会保留大量的旧版本数据,可能会导致回滚段空间不足,影响系统性能。