MYSQL事务的底层原理( 三 )

  • 如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本 。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录;
  • 在 MySQL 中,READ COMMITTED 和 REPEATABLE READ 隔离级别的一个非常大的区别就是它们生成 ReadView 的时机不同 。
    还是以表 mvcc_test 为例,假设现在表 mvcc_test 中只有一条由事务 id 为 50 的事务插入的一条记录 , 接下来看一下 READ COMMITTED 和 REPEATABLE READ 所谓的生成 ReadView 的时机不同到底不同在哪里 。
    READ COMMITTED:每次读取数据前都生成一个 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事务的底层原理

    文章插图
    假设现在有一个使用 READ COMMITTED 隔离级别的事务开始执行:
    -- 使用 READ COMMITTED 隔离级别的事务
    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 列表内,所以不符合可见性要求第 4 条:如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id之间 min_trx_id < trx_id < max_trx_id,那就需要判断一下trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在 , 说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问 。根据 roll_pointer 跳到下一个版本 。
    下一个版本的列 name 的内容是 habit_trx_id_70_01,该版本的 trx_id 值也为 70,也在 m_ids 列表内,所以也不符合要求,继续跳到下一个版本 。
    下一个版本的列 name 的内容是 habit , 该版本的 trx_id 值为 50,小于 ReadView 中的 min_trx_id 值,所以这个版本是符合要求的第 2 条:如果被访问版本的 trx_id 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问 。最后返回的版本就是这条列 name 为 habit 的记录 。
    之后,把事务 id 为 70 的事务提交一下,然后再到事务 id 为 90 的事务中更新一下表 mvcc_test 中 id 为 1 的记录:
    【MYSQL事务的底层原理】-- T 90
    UPDATEmvcc_test SETname='habit_trx_id_90_01'WHEREid=1;
    UPDATEmvcc_test SETname='habit_trx_id_90_02'WHEREid=1;
    此时表 mvcc 中 id 为 1 的记录的版本链就长这样:
    MYSQL事务的底层原理

    文章插图
    然后再到刚才使用 READ COMMITTED 隔离级别的事务中继续查找这个 id 为 1 的记录,如下:
    -- 使用 READ COMMITTED 隔离级别的事务
    BEGIN;
    -- SELECE1:Transaction 70、90 均未提交
    SELECT*FROMmvcc_test WHEREid=1;-- 得到的列 name 的值为'habit'
    -- SELECE2:Transaction 70 提交,Transaction 90 未提交
    SELECT*FROMmvcc_test WHEREid=1;-- 得到的列 name 的值为'habit_trx_id_70_02'
    这个 SELECE2 的执行过程如下:
    在执行 SELECT 语句时又会单独生成一个 ReadView,该 ReadView 的 m_ids 列表的内容就是 [90],min_trx_id 为 90,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,小于 ReadView 中的 min_trx_id 值 90,所以这个版本是符合要求的,最后返回这个版本中列 name 为 habit_trx_id_70_02 的记录 。


    推荐阅读