PS:本文需要一点MySQL原理的前置知识

MVCC

  MVCC(Multi Version Concurrency Control),也叫多版本并发控制,顾名思义,就是通过记录的多个版本来实现数据库的并发控制
  MySQL之所以可以在可重复读隔离阶段就解决幻读问题,主要依靠于MVCC锁机制锁机制是一种悲观锁的实现方式,而MVCC是一种乐观锁的实现方式,使数据库具有更好的并发性能。
  MVCC主要依赖于三样东西实现,分别是记录的隐藏字段(聚簇索引才有)、undo logRead View,接下来让我们一个个来解释一下这三样东西。

记录的隐藏字段

  在聚簇索引中,每条记录都有额外的三个隐藏字段,分别是db_row_iddb_trx_iddb_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_iddb_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

  Read View中有四个比较重要的内容,分别是creator_trx_idtrx_idsup_limit_idlow_limit_id

  • creator_trx_id:创建当前Read View的事务id
  • trx_ids:创建当前Read View时,所有的活跃事务id列表
  • up_limit_id:创建当前Read View时,最小的活跃事务id
  • low_limit_id:创建当前Read View时,预分配的下一个事务id(比当时活跃的最大事务id还大)。

Read View规则:

  • 被访问记录的db_trx_idRead View中的creator_trx_id相同,代表当前事务访问的是自己修改过记录,所以该记录可以被当前事务访问
  • 被访问记录的db_trx_id小于Read Viewup_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 Viewtrx_ids
    • 被访问记录的db_trx_id不在Read View中的trx_ids中,则代表最后修改该记录的事务在当前Read View创建之前已经提交,所以该记录可以被当前事务访问
    • 被访问记录的db_trx_idRead 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_iddb_roll_pointer隐藏字段的,那么二级索引该如何使用MVCC呢?
  答案是二级索引在页级别有一个PAGE_MAX_TRX_ID属性,代表最后对当前页进行修改的事务id。在二级索引中,MVCC的流程是Read ViewPAGE_MAX_TRX_ID进行比较的。

  • 数据页的PAGE_MAX_TRX_ID符合Read View的可见规则,则代表事务可以对该页中的数据进行操作。
  • 数据页的PAGE_MAX_TRX_ID不符合Read View的可见规则,则需要进行回表操作,在聚簇索引中进行MVCC操作。

  我们要知其然知其所以然,为什么二级索引不采用聚簇索引一样的解决方案,而是要用这样的解决方案呢?

  • PAGE_MAX_TREX_ID还应用于二级索引INSERT语句的隐式锁的处理方案,原理自行查阅。

为什么二级索引要用这样的解决方案呢?

  答案是为了性能,因为聚簇索引是唯一的,而二级索引不是唯一的,可以有多个,如果都采用聚簇索引的方式来维护二级索引的MVCC,那么性能是非常差的。
  从聚簇索引的方案二级索引的方案,可以理解为从行锁变为表锁,标记的范围更大了,但是性能好了,因为不需要针对二级索引每条记录进行维护db_trx_iddb_roll_pointer字段了。遇到二级索引的MVCC方案无法处理的情况就进行回表操作,在聚簇索引上进行MVCC即可。

总结

  每次学习到原理部分,都会有恍然大悟的感觉,更会感叹作者设计的巧妙,加油加油!!!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐