MYSQL事务的底层原理( 四 )


以此类推,如果之后事务 id 为 90 的记录也提交了 , 再次在使用 READ COMMITTED 隔离级别的事务中查询表 mvcc_test 中 id 值为 1 的记录时,得到的结果就是 habit_trx_id_90_02 了 。
总结:使用 READ COMMITTED 隔离级别的事务在每次查询开始时都会生成一个独立的 ReadView 。
REPEATABLE READ:在第一次读取数据时生成一个 ReadView;
对于使用 REPEATABLE READ 隔离级别的事务来说,只会在第一次执行查询语句时生成一个 ReadView,之后的查询就不会重复生成了 。
比方说现在系统里有两个事务 id 分别为 70、90 的事务在执行:
-- T 70
UPDATEmvcc_test SETname='habit_trx_id_70_01'WHEREid=1;
UPDATEmvcc_test SETname='habit_trx_id_70_02'WHEREid=1;
此时表 mvcc_test 中 id 为 1 的记录得到的版本链表如下所示:

MYSQL事务的底层原理

文章插图
?假设现在有一个使用 REPEATABLE READ 隔离级别的事务开始执行:
-- 使用 REPEATABLE READ 隔离级别的事务
BEGIN;
-- SELECE1:Transaction 70、90 未提交
SELECT*FROMmvcc_test WHEREid=1;-- 得到的列name 的值为'habit'
这个 SELECE1 的执行过程如下:
在执行 SELECT 语句时会先生成一个 ReadView,ReadView 的 m_ids 列表的内容就是 [70, 90] , min_trx_id 为 70,max_trx_id 为 91,creator_trx_id 为 0 。
然后从版本链中挑选可见的记录,从图中可以看出 , 最新版本的列 name 的内容是 habit_trx_id_70_02,该版本的 trx_id 值为 70,在 m_ids 列表内 , 所以不符合可见性要求,根据 roll_pointer 跳到下一个版本 。
下一个版本的列 name 的内容是 habit_trx_id_70_01 , 该版本的 trx_id 值也为 70,也在 m_ids 列表内,所以也不符合要求 , 继续跳到下一个版本 。
下一个版本的列 name 的内容是 habit,该版本的 trx_id 值为 50,小于 ReadView 中的 min_trx_id 值 , 所以这个版本是符合要求的,最后返回的就是这条列 name 为 habit 的记录 。
之后 , 把事务 id 为 70 的事务提交一下 , 然后再到事务 id 为 90 的事务中更新一下表 mvcc_test 中 id 为 1 的记录:
-- 使用 REPEATABLE READ 隔离级别的事务
BEGIN;
UPDATEmvcc_test SETname='habit_trx_id_90_01'WHEREid=1;
UPDATEmvcc_test SETname='habit_trx_id_90_02'WHEREid=1;
此刻,表 mvcc_test 中 id 为 1 的记录的版本链就长这样:
MYSQL事务的底层原理

文章插图
然后再到刚才使用 REPEATABLE READ 隔离级别的事务中继续查找这个 id 为 1 的记录,如下:
-- 使用 REPEATABLE READ 隔离级别的事务
BEGIN;
-- SELECE1:Transaction 70、90 均未提交
SELECT*FROMmvcc_test WHEREid=1;-- 得到的列 name 的值为'habit'
-- SELECE2:Transaction 70 提交,Transaction 90 未提交
SELECT*FROMmvcc_test WHEREid=1;-- 得到的列 name 的值为'habit'
这个 SELECE2 的执行过程如下:
因为当前事务的隔离级别为 REPEATABLE READ,而之前在执行 SELECE1 时已经生成过 ReadView 了,所以此时直接复用之前的 ReadView,之前的 ReadView 的 m_ids 列表的内容就是 [70, 90],min_trx_id 为 70,max_trx_id 为 91,creator_trx_id 为 0 。
然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列 name 的内容是 habit_trx_id_90_02,该版本的 trx_id 值为 90,在 m_ids 列表内,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本 。
下一个版本的列 name 的内容是 habit_trx_id_90_01,该版本的 trx_id 值为 90,也在 m_ids 列表内,所以也不符合要求 , 继续跳到下一个版本 。
下一个版本的列 name 的内容是 habit_trx_id_70_02,该版本的 trx_id 值为 70,而 m_ids 列表中是包含值为 70 的事务 id 的 , 所以该版本也不符合要求,同理下一个列 name 的内容是 habit_trx_id_70_01 的版本也不符合要求 。继续跳到下一个版本 。
下一个版本的列 name 的内容是 habit , 该版本的 trx_id 值为 50 , 小于 ReadView 中的 min_trx_id 值 70 , 所以这个版本是符合要求的 , 最后返回给用户的版本就是这条列 name 为 habit 的记录 。
也就是说两次 SELECT 查询得到的结果是重复的 , 记录的列 name 值都是 habit,这就是可重复读的含义 。如果之后再把事务 id 为 90 的记录提交了,然后再到刚才使用 REPEATABLE READ 隔离级别的事务中继续查找这个 id 为 1 的记录,得到的结果还是 habit 。


推荐阅读