MVCC的实现原理
mvcc的实现,基于undolog 、版本链 、readview。(具体就如下图)
在 MySQL 存储的数据中,MySQL 会默认添加一些额外的隐含字段(Hidden Field),包括 trx_id
和 roll_pointer
等字段。这些字段大多数是用于支持事务和数据恢复等功能。
trx_id
:是一个系统自动生成的、递增的整数,用于标识当前操作是在哪个事务中执行的。每开始一个新的事务,事务 ID 就会自增 1。因为事务 ID 是系统自动生成的,所以我们一般不需要对其进行手动修改。roll_pointer
:是一个指针,用来定位上一个版本的数据。当你对一条记录进行修改时,MySQL 会在内部为该记录创建一份副本并保存到 redo log 中,同时记录下这个副本在 redo log 中的位置,这就是roll_pointer
所表示的内容。如果发生了回滚操作,MySQL 会通过roll_pointer
来查找对应记录的上一个版本,并将其恢复回去。
有了这么多的版本之后,当有一个select查询的时候,具体查询的哪个版本呢?
readview读视图来帮我们解决这个问题
当我们用select读取数据时,这一时刻的数据会有很多个版本(例如上图有四个版本),但我们并不知道读取哪个版本,这时就靠readview来对我们进行读取版本的限制,通过readview我们才知道自己能够读取哪个版本。
在事务select查询数据时,就会构造一个readview,里面就记录了该条数据版本链的一些统计值,这样在后续查询处理时就无需遍历所有版本链了。
在一个readview快照中具体包括以下这些字段:
对readview中的参数做一些解释
**m_ids:**活跃的事务就是指还没有commit的事务(会像一个集合一样展示出那些活跃【未提交】的事务)。
**max_trx_id:**例如m_ids中的事务id为(1,2,3),那么下一个应该分配的事务id就是4,max_trx_id就是4。
creator_trx_id:当前执行select读这个操作的事务的id。
readview具体判断版本链中的哪个版本可用(重点!)
四步查找规则------
第一步:判断该版本是否由当前事务创建
若creator_trx_id=【当前版本trx_id】,意味着读取自己修改的数据,当然可以直接访问。如果不等于当前版本的trx_id则跳到第二步
第二步:【当前版本trx_id】是否小于min_trx_id
【当前版本trx_id】<min_trx_id,说明该版本在生成readview之前已经提交,可以直接访问。如果不是则进行第三步
第三步:【当前版本trx_id】是否大于max_trx_id
【当前版本trx_id】>max_trx_id,说明该版本在生成readview之后才开启,肯定不能被当前事务访问,所以此时就不需要进行第四步再去遍历判断下一个版本。如果当前版本的事务id小于最大事务id则可以继续进行第四步
第四步:min_trx_id<【当前版本trx_id】<max_trx_id
如果当前版本不在活跃事务列表当中,则意味着创建readview的时候,该版本已经被提交,可以直接访问。
如果在活跃事务列表当中,则按照版本链遍历去判断下一个版本,直到找到首个符合要求的版本。
从上到下分别为(1)(2)(3)(4),再依次进行一遍解释,可以加深一遍印象
trx_id表示要读取的事务id
(1)如果要读取的事务id等于进行读操作的事务id,说明是我读取我自己创建的记录,那么为什么不可以呢。
(2)如果要读取的事务id小于最小的活跃事务id,说明要读取的事务已经提交,那么可以读取。
(3)max_trx_id表示生成readview时,分配给下一个事务的id,如果要读取的事务id大于max_trx_id,说明该id已经不在该readview版本链中了,故无法访问。
(4)m_ids中存储的是活跃事务的id,如果要读取的事务id不在活跃列表,那么就可以读取,反之不行。
mvcc如何实现RC和RR的隔离级别
(1)RC 的隔离级别下,每个快照读 都会生成并获取最新的readview。
(2)RR 的隔离级别下,只有在同一个事务 的第一个快照读 才会创建readview ,之后的每次快照读都使用的同一个readview ,所以每次的查询结果都是一样的。