轻松理解MySQL的MVCC机制
MVCC主要依赖于三样东西实现,分别是`记录的隐藏字段`(聚簇索引才有)、`undo log`、`Read View`,接下来让我们一个个来解释一下这三样东西。
PS:本文需要一点MySQL原理的前置知识
MVCC
MVCC(Multi Version Concurrency Control),也叫多版本并发控制,顾名思义,就是通过记录的多个版本来实现数据库的并发控制。
MySQL之所以可以在可重复读隔离阶段就解决幻读问题,主要依靠于MVCC和锁机制,锁机制是一种悲观锁的实现方式,而MVCC是一种乐观锁的实现方式,使数据库具有更好的并发性能。
MVCC主要依赖于三样东西实现,分别是记录的隐藏字段(聚簇索引才有)、undo log、Read View,接下来让我们一个个来解释一下这三样东西。
记录的隐藏字段
在聚簇索引中,每条记录都有额外的三个隐藏字段,分别是db_row_id、db_trx_id、db_roll_pointer。
db_row_id:当数据表中不存在主键或唯一索引字段时,InnoDB就会自动该隐藏字段作为表中的主键(当存在主键或唯一索引字段时则不会生成该隐藏字段)。db_trx_id:记录最后修改当前记录的事务id,每次事务修改聚簇索引记录时,都会把当前事务id赋值给db_trx_id。db_roll_pointer:每次对聚簇索引中的记录进行修改时,都会先把旧的版本写入到undo log中,roll_pointer就相当于一个指针,可以通过他找到历史版本的信息。
MVCC不需要使用到db_row_id,只会使用到db_trx_id和db_roll_pointer两个隐藏字段。
undo log
undo log也叫做回滚日志,它是事务原子性的保证,它主要的作用是用于回滚事务和MVCC读取历史版本的数据。
Read View
Read View是指事务在使用MVCC机制进行快照读操作时产生的读视图,主要用于判断当前事务是否可以读取对应的记录。
- 在
读已提交隔离级别下,在事务中每次读操作(SELECT、UPDATE、DELETE语句都包含读操作)都会产生一个新的Read View。 - 在
可重复读隔离级别下,只有事务中的第一次使用读操作(SELECT、UPDATE、DELETE语句都包含读操作)时,才会产生Read View。- PS:在
可重复读隔离级别下,可以在开启事务的时候可以使用WITH CONSISTENT SNAPSHOT参数,则代表开启事务的时候就创建Read View,不需要等到事务中的第一次读操作才会产生,即START TRANSACTION WITH CONSISTENT SNAPSHOT。
- PS:在
Read View中有四个比较重要的内容,分别是creator_trx_id、trx_ids、up_limit_id、low_limit_id。
creator_trx_id:创建当前Read View的事务idtrx_ids:创建当前Read View时,所有的活跃事务id列表up_limit_id:创建当前Read View时,最小的活跃事务idlow_limit_id:创建当前Read View时,预分配的下一个事务id(比当时活跃的最大事务id还大)。
Read View规则:
- 被访问记录的
db_trx_id与Read View中的creator_trx_id相同,代表当前事务访问的是自己修改过记录,所以该记录可以被当前事务访问 - 被访问记录的
db_trx_id小于Read View中up_limit_id,代表最后修改该记录的事务在当前Read View创建之前已经提交,所以该记录可以被当前事务访问 - 被访问记录的
db_trx_id大于等于Read View中的low_limit_id,代表最后修改该记录的事务在当前Read View创建之后才开启,所以该记录不可以被当前事务访问 - 被访问记录的
db_trx_id位于Read View中的[up_limit_id,low_limit_id)区间,则需要进一步判断被访问记录的trx_id是否在Read View的trx_ids中- 被访问记录的
db_trx_id不在Read View中的trx_ids中,则代表最后修改该记录的事务在当前Read View创建之前已经提交,所以该记录可以被当前事务访问 - 被访问记录的
db_trx_id在Read View中的trx_ids中,则代表最后修改该记录的事务在当前Read View创建的时候还没有提交,所以该记录不可以被当前事务访问
- 被访问记录的
MVCC流程
上面已经介绍了MVCC依赖的三样东西,现在就让我们来解析一下事务使用MVCC访问记录的流程:
- 1、事务获取自己的事务id
- 2、事务获取
Read View读已提交隔离级别每次读操作都会生成,可重复读隔离级别只有第一次读操作才会生成
- 3、使用查询到的记录与
Read View中的事务信息进行比较- 找到符合
Read View规则的记录,不符合则使用undo log获取历史快照,再次进行Read View比较
- 找到符合
- 4、返回符合
Read View规则的记录
二级索引解决方案
以上说的都是对聚簇索引中记录的操作方式,二级索引是不存在db_trx_id、db_roll_pointer隐藏字段的,那么二级索引该如何使用MVCC呢?
答案是二级索引在页级别有一个PAGE_MAX_TRX_ID属性,代表最后对当前页进行修改的事务id。在二级索引中,MVCC的流程是Read View与PAGE_MAX_TRX_ID进行比较的。
- 数据页的
PAGE_MAX_TRX_ID符合Read View的可见规则,则代表事务可以对该页中的数据进行操作。 - 数据页的
PAGE_MAX_TRX_ID不符合Read View的可见规则,则需要进行回表操作,在聚簇索引中进行MVCC操作。
我们要知其然知其所以然,为什么二级索引不采用聚簇索引一样的解决方案,而是要用这样的解决方案呢?
PAGE_MAX_TREX_ID还应用于二级索引对INSERT语句的隐式锁的处理方案,原理自行查阅。
为什么二级索引要用这样的解决方案呢?
答案是为了性能,因为聚簇索引是唯一的,而二级索引不是唯一的,可以有多个,如果都采用聚簇索引的方式来维护二级索引的MVCC,那么性能是非常差的。
从聚簇索引的方案到二级索引的方案,可以理解为从行锁变为表锁,标记的范围更大了,但是性能好了,因为不需要针对二级索引每条记录进行维护db_trx_id、db_roll_pointer字段了。遇到二级索引的MVCC方案无法处理的情况就进行回表操作,在聚簇索引上进行MVCC即可。
总结
每次学习到原理部分,都会有恍然大悟的感觉,更会感叹作者设计的巧妙,加油加油!!!
更多推荐



所有评论(0)