功能
通过维护数据历史版本,从而解决并发情况下的读一致性问题。
核心内容
主要包含了:
- 版本链:每一个数据行包含两个隐藏数据,事务id和回滚指针,将每一次更改通过回滚指针连起来。
- 事务链表:事务开始的时候,就会加入事务链表。提交后,就会在事务链表中删除。
- ReadView:一个事务在开始执行SQL的时候,会先创建一个ReadView。该ReadView存储着事务链表。通过判断要访问的事务是否在ReadView中,从而判断该事务里的数据该不该读。
版本链
每一个数据行有两个隐藏的数据列(实际上,如果数据行没有主键且没有唯一列,InnoDB除了以上两个以外,还会新建一个隐藏列作为索引主键):
- trx_id(事务id)
- roll_pointer(回滚指针)
假设hero
表中只有一行记录,当时插入的事务id为80。此时,该条记录的示例图如下:
假设之后两个事务id
分别为100
、200
的事务对这条记录进行UPDATE
操作,操作流程如下:
由于每次变动都会先把undo
日志记录下来,并用roll_pointer
指向undo
日志地址。因此可以认为,对该条记录的修改日志串联起来就形成了一个版本链
,版本链的头节点就是当前记录最新的值。如下:
事务链表
MySQl中的事务在开始到提交的时候都会存进事务链表中。用来表示当前活跃的事务。 一旦提交就会从事务链表中清除。
ReadView
- 参数
- trx_ids:用来表示当前活跃的事务
- up_trx_id:trx_ids中的最大值
- low_trx_id:trx_ids中的最小值
- creator_trx_id:创建该ReadView的事务id
- 描述
一个事务在开始执行SQL语句的时候,会创建一个ReadView。有了ReadView之后,就可以判断某个版本的记录是否对当前事务可见。- 被访问的记录trx_id与creator_trx_id相同:可以访问。
- 被访问的记录trx_id小于low_trx_id:说明已经提交,可以访问。
- 被访问的记录trx_id大于等于up_trx_id:可能是在创建ReadView之后,提交情况未知,不可访问。
- 被访问的记录trx_id在low_trx_id和ip_trx_id之间
- 如果在trx_ids中,说明未提交,不可访问
- 如果不在trx_ids中,说明已经提交,可以访问
MVCC与隔离级别
- Read Committed:在每一次select的时候,会创建readview表。当要访问某个数据的时候,先去查看该数据行的版本链,
- 如果最新的版本链的事务id小于readview的low_trx_id,那么表示最近修改该数据行的事务已经提交,可以访问;
- 如果大于readview的max_trx_id,那么不可以访问,顺延回滚指针查看更老的版本数据行;
- 如果在readview trx_ids里,那么说明该事物活跃,不可以访问,顺延回滚指针;
- 如果在(low_trx_id, up_trx_id)范围里,且不在m_id,说明该事务已经提交,可以访问。
- Repeatable Read:只在第一次读的时候,生成一个ReadView,即使readview范围里,有事务提交了,也不会去读,从而保证了,读到的数据前后一致性。
- 在Repeatable Read事务机制下,使用Next-Key Lock(锁定一个范围) 可以解决幻读。
- 对于快读,select,可以直接使用mvcc的特性进行读;
- 对于当前读,增删改,那么就需要使用Next-Key Lock加锁。也就是当一个事务1正在select一个事务的时候,会对相应的查找范围加一个next-key的锁,当事务2想要对这个范围的加入新的数据行是会被拒绝的。