参考资料:
参考视频(注意第二个视频关于幻读的讲解是错误的,详情见本文)
redoLog的结构详解
参考资料
学习内容:
1. MVCC要解决的问题
MVCC要解决的问题是,在不产生脏读等数据库问题的前提下,数据库的查询语句和更改语句不相互阻塞的情况;
在InnoDB中,MVCC仅仅存在于已提交读和可重复读两个隔离级别。
2. MVCC的实现机制
MVCC的实现完全依赖于undolog链表和ReadView两大板块
3. UndoLog链表
(1)InnoDB数据库中的每一行,都有三个隐藏字段
- 事务ID(DB_TRX」D):增删改都会默认开启事务,数据行每参与一次事务,就会更新改行的事务ID.
- 回滚指针():指向旧版本的数据
- Row_ID: 当InnoDB表没有规定主键,并且找不到非空列来构建聚簇索引,那么就会使用Row_ID作为改行的唯一标识,来构建聚簇索引
(2) Undo_Log的结构如下
数据库中的数据,在参与事务前(修改前),都会记录一个undo_log(包括原先的回滚指针和事务ID)
然后事务修改数据,并且更新隐藏的事务ID和回滚指针(回滚指针指向undo_log中的上一个版本数据)
就这样不断地更新,就生成了undo_log版本链表
其实undo_log版本链的结构要复杂的多,关于undo_log的详细介绍见前面的笔记
(3) 当前读和快照读
- 不加锁的select就是快照读(读取MVCC中undo_log的版本链中的数据)
- 加锁的select 和增删改等其他语句就是当前读(直接读取数据库表中的数据)
(4)Read view表
① ReadView作用
当相同的数据被很多事务操作的时候,UndoLog就会变得错综复杂且冗长,那么在进行select快照读的时候,往往很难判断应该读取哪个快照;
所以在进行select快照读的时候,会生成一个readview,用来帮助确定是需要读取哪个undoLog快照
②ReadView的结构
ReadView其实是事务ID的集合,其中包括当前正在执行未提交的事务ID列表,创建ReadView的事务ID等
③ ReadView生成时机
RC(已提交读):ReadView在RC(已提交读)隔离级别下,每进行一次select快照读,就会生成一个ReadView
RR(可重复读): ReadView在RR(可重复读)隔离级别下,每一个事务只有一个readView,在第一次进行select快照读时生成,后续的快照读,共享这一份readView
④ ReadView的执行逻辑
当我们进行select快照读时,就会使用ReadView,顺着当前数据以及它所连接的UndoLog版本链,进行事务ID的比对,选出对应的数据版本并返回;
具体的对比逻辑是:
顺着当前数据以及UndoLog版本链往下找,
- 如果事务ID小于readView的事务ID列表中的最小值,表示该数据已提交,那么就进行返回;
- 如果事务ID大于readView的事务ID列表中的最大值,该数据不可以返回,继续寻找;
- 如果事务ID大于等于readView的事务ID列表中的最小值且小于等于事务ID列表中的最大值:
- 如果事务ID在readView的事务ID列表中,且事务ID等于创建readView的事务ID,那么就进行返回,否则不可以返回,继续寻找
- 如果事务ID不在readView的事务ID列表中,那么就进行返回;
⑤ InnoDb的MVCC在RR(可重复读)隔离级别下的幻读问题
结论:InnoDB在RR隔离级别下,仅仅解决了部分幻读问题,并没有解决全部
下面将通过举例来说明(数据库在RR隔离级别下)
假设数据库中有原始数据
1)发生幻读的情况
具体步骤如下:
首先,事务202会进行一次select快照读,并生成ReadView
查询结果为:
然后事务201进行一次数据插入,并提交,此时的数据库为:
然后事务202进行了一次数据更新,这时就会产生UndoLog
最后事务202进行select快照读,因为此时隔离级别为RR(可重复读),所以共用着一份readView
readView会顺着数据和redoLog链进行查询
因为202属于区间[201,202],且202为当前事务ID,
所以查询到的数据为:
此时便发生了幻读
2) 解决幻读的情况
具体步骤如下:
- 首先,事务202会进行一次select快照读,并生成ReadView
查询结果为:
b.然后事务201进行一次数据插入,并提交,此时的数据库为:
然后事务202进行了一次数据更新,这时就会产生UndoLog
- 最后事务202进行select快照读,因为此时隔离级别为RR(可重复读),所以共用着一份readView
e.readView会顺着数据和redoLog链进行查询
因为202属于区间[201,202],且202为当前事务ID,201虽然属于这个区间,但是不等于当前事务ID,所以只显示202的数据
所以得到的结果为:
并没有发生幻读