首先我们要先清楚READ VIEW是啥
READ VIEW 是快照,就是像拍照一样 把这一瞬间的景象(数据)保存下来
我们的事务隔离等级中的 可重复读和读提交都是通过READ VIEW实现的
那 Read View 到底是个什么东西?
m_ids:指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务。
min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
creator_trx_id :指的是创建该 Read View 的事务的事务 id。
这是InnoDB储存的行结构
其中有三个隐藏列
row_id 如果我们建表的时候指定了主键或者唯一约束列,那么就没有 row_id 隐藏字段了。如果既没有指定主键,又没有唯一约束,那么 InnoDB 就会为记录添加 row_id 隐藏字段。
trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里;
roll_ptr,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。
在创建 Read View 后,我们可以将记录中的 trx_id 划分这三种情况:
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:
- 如果记录的 trx_id 值小于 Read View 中的
min_trx_id
值,表示这个版本的记录是在创建 Read View 前已经提交的事务生成的,所以该版本的记录对当前事务可见。 - 如果记录的 trx_id 值大于等于 Read View 中的
max_trx_id
值,表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以该版本的记录对当前事务不可见。 - 如果记录的 trx_id 值在 Read View 的
min_trx_id
和max_trx_id
之间,需要判断 trx_id 是否在 m_ids 列表中:- 如果记录的 trx_id 在
m_ids
列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。 - 如果记录的 trx_id 不在
m_ids
列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。
- 如果记录的 trx_id 在
哦哦哦 我终于转过弯来了 是执行一个事务的时候创建一个READ VIEW(「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。)
这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。
细说读提交和可重复读
读提交
读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View。
假设事务 A (事务 id 为51)启动后,紧接着事务 B (事务 id 为52)也启动了,接着按顺序执行了以下操作:
- 事务 B 读取数据(创建 Read View),小林的账户余额为 100 万;
- 事务 A 修改数据(还没提交事务),将小林的账户余额从 100 万修改成了 200 万;
- 事务 B 读取数据(创建 Read View),小林的账户余额为 100 万;
- 事务 A 提交事务;
- 事务 B 读取数据(创建 Read View),小林的账户余额为 200万
我们来思考一下 为什么会这样
首先 当读取数据时 创建一个READ VIEW 此时READ VIEW:(事务A已经启动)
creator_trx_id: 52
m_ids:51,52
min_trx_id:51
max_trx_id:53
此时命中
但是此时还没发生改动 所以无所谓
第二次读取事务 READ VIEW与上次相同 但是这次发生了数据修改 此时trx_id在m_ids列表中 也就表示不可见(和上次命中一样等待逻辑空间) 沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id 「小于」事务 B 的 Read View 中的 min_trx_id 值的第一条记录,所以事务 B 能读取到的是 trx_id 为 50 的记录,也就是小林余额是 100 万的这条记录。
第三次读取事务的时候 A已经提交了事务 所以READ VIEW参数为
creator_trx_id: 52
m_ids:52
min_trx_id:52
max_trx_id:53
之前我没想明白啥意思 为啥会有
这种情况 我现在明白了 当当前READ VIEW存在期间 某个事务结束了 但是最早的事务还没结束
所以min_trx_id 没变 结束的id还是>最小的id 但是已经结束了(不在m_dis里面了)
此时51满足分支
可见 也就查到了 200万的记录
一旦READVIEW 确定下来就不会更改了 这里的只不过是重复创建而已
可重复读
可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View。
还是先创建个A事务(id:51) B事务(id:52)
A事务的READ VIEW
creator_trx_id: 51
m_ids:51
min_trx_id:51
max_trx_id:52
B事务的READ VIEW
creator_trx_id: 52
m_ids:51 52
min_trx_id:51
max_trx_id:53
接着,在可重复读隔离级别下,事务 A 和事务 B 按顺序执行了以下操作:
- 事务 B 读取小林的账户余额记录,读到余额是 100 万;
- 事务 A 将小林的账户余额记录修改成 200 万,并没有提交事务;
- 事务 B 读取小林的账户余额记录,读到余额还是 100 万;
- 事务 A 提交事务;
- 事务 B 读取小林的账户余额记录,读到余额依然还是 100 万;
我们来看B的第一次读取 没区别 还没更改
第二次读取的收 用的还是最开始的READ VIEW 51在列表中 不可见 所以还是通过rool_ptr找上一个版本
第三次用的 还是这个快照 没区别