深挖MySQL —— 事务(三)事务一致性的保证与MVCC的基石:undo log

        undo log简介

        与拥有独立日志文件的redo日志不同,回滚日志undo log记录在公共表空间的回滚段undo segment中。顾名思义,回滚日志的用途主要就是用于回滚,其次innodb还将它用于实现多版本并发控制MVCC

        回滚功能保障了数据库事务的一致性C,所谓一致性指的是数据库事务能从一个正确的状态转换到另一个正确的状态,比方说张三给李四转账时能确保张三账号上减少100块的时候李四账号多了100块。回滚功能可以保障事务中间执行失败后,能将整个事务的sql都回滚,确保数据库不会转换到一个错误的状态。

        undo log的产生过程及文件结构

        了解回滚日志之前,我们先看看innodb表的三个隐藏字段:行ID(DB_ROW_ID)、事务ID(DB_TRX_ID)、回滚指针(DB_ROLL_PTR):

        DB_ROW_ID:如果表没有设置主键,用来作为记录的主键,因为Innodb使用聚簇索引的方式存储,记录必须有主键。
        DB_TRX_ID:记录最近修改这行记录事务的id。
        DB_ROLL_PTR:指向这行记录的前一个版本。 

        提前科普下事务的隔离级别(有个概念就好,后面细聊):read uncommited读未提交;read commited读已提交,会使用MVCC实现快照读;repeatable可重复读,会使用MVCC实现快照读;serializable序列化,效率最低。下面来看看innodb回滚日志产生过程,看看回滚日志的结构以及什么是MVCC:

        1、新建innodb引擎表:业务字段1,业务字段2;自动带上隐藏字段DB_ROW_ID,DB_TRX_ID,DB_ROLL_PTR。此时为空表,没有任何数据。

        2、开始事务执行insert命令,插入一条数据,由于插入之前,数据是不存在的,也不可能被其它事务看见,所以不存在被MVCC占用的情况,因此insert产生的undo log在事务提交后可以直接被回收,数据如下:

         3、事务A与事务B的隔离级别分别为read commited和repeatable,都执行select * from table where 业务字段1=数据1;这是他们获得相同的数据结果:{业务字段1:数据1,业务字段2:数据2}

        4、事务C执行update table set 业务字段1=数据a where 业务字段1=数据1;此时未提交,行数据被事务C加了写锁(X锁),业务字段和三个隐藏字段数据被修改,原来的行数据移到回滚日志,当前行的回滚指针指向了初始版本的行数据,回滚日志的类型为(update/delete),此时由于初始行被其它事务MVCC占用了,所以回滚日志不能被回收

        那么,此时事务A与事务B再次执行select * from table where 业务字段1=数据1;的时候会产生什么结果?

        然后又启动两个事务A1(隔离级别依然read commited)事务B1(隔离级别repeatable),两个事务也执行select * from table where 业务字段1=数据1;这个时候又会有什么样的结果?

        事务C这个时候执行提交操作,此时事务A、事务B,事务A1、事务B1执行select * from table where 业务字段1=数据1;又会有什么结果?

        分析之前,我们先了解一下读视图read view,读视图是快照读的时候产生的视图,里面包含了:

        trx_list:记录产生读视图时的系统活跃的事务ID(包括自己)
        up_limit_id:记录了列表中最小的事务ID
        low_limit_id:记录了系统尚未分配的下一个事务ID

        其中repeatable级别的读视图产生在事务第一次执行快照读的时间点,注意不是事务开启的时间点;read commited会在每次快照读都产生新的read view。

        然后我们来看看read view读数据的原则

        (1)首先比较“行数据的事务ID”和up_limit_id,如果“行数据的事务ID”<up_limit_id,证明此行版本在read view产生前就存在了,可以读取。如果大于等于,进入下一判断。

        (2)比较“行数据事务ID”和low_limit_id,如果“行数据事务ID”>=low_limit_id,证明此行数据是在read view产生后才出现的,数据绝对不可读取。如果小于,则进入下一判断。

        (3)判断“行数据事务ID”是否在trx_list列表中,如果在列表中,代表在read view生成的时候行数据所属的事务还是未提交状态的,因此不可见;如果不在列表中,则数据可以读取。

        (4)经过1、2、3的判断,数据还是不可读取,根据回滚指针找到上一版本数据,继续进行1、2、3的判断。

        现在,我们根据read view读取数据的原则来分析各情况读取行数据的情况,有搭建MySQL环境的同学可以建一张临时表验证分析结果是否准确:

        事务C的update操作提交前(为了方便以下事务A的ID直接用事务A来表示,根据事务开启的顺序,初始事务<事务A<事务B<事务C<事务A1<事务B1):

        事务A执行select进行快照读,隔离级别read commited会在每次快照读都产生新的read view,因此trx_list=[事务A、事务B、事务C],up_limit_id=事务A,low_limit_id=事务A1,首先进行第一步比较,最新版本数据的事务ID(事务C)>up_limit_id,数据无法读取,因此进入下一步骤判断;事务C<low_limit_id,进入下一步骤;判断事务C在trx_list里面,数据无法读取;根据指针到上一版本的数据,进入第一步判断,当前版本事务ID(初始事务)<up_limit_id,数据可以读取,读取结果:{业务字段1:数据1,业务字段2:数据2}

        事务B执行select进行快照读,隔离级别repeatable的读视图产生在事务第一次执行快照读的时间点,因此trx_list=[事务A、事务B],up_limit_id=事务A,low_limit_id=事务C,数据绝对不可读取;根据指针到上一版本的数据,进入第一步判断,当前版本事务ID(初始事务)<up_limit_id,数据可以读取,读取结果:{业务字段1:数据1,业务字段2:数据2}

        如果此时启动事务A1、事务B1,产生的read view可以参照事务A,唯一不同的是low_limit_id的值不是事务A1而是一个更大的未分配的事务ID。因此读取结果也是:{业务字段1:数据1,业务字段2:数据2}

        顺便一提,事务C此时执行回滚操作时,根据回滚指针指向,数据恢复到上一版本状态(回滚:我不是undo log的主要功能吗?为何顺便一提?我的牌面呢?!!!)

        事务C执行提交操作,现在我们继续进行快照读分析:

        事务A执行select进行快照读,隔离级别read commited会在每次快照读都产生新的read view,因此trx_list=[事务A、事务B],up_limit_id=事务A,low_limit_id=事务A1,首先进行第一步比较,最新版本数据的事务ID(事务C)>up_limit_id,数据无法读取,因此进入下一步骤判断;事务C<low_limit_id,进入下一步骤;判断事务C不在trx_list里面,数据可以读取,但是行数据不满足where 业务字段1=数据1的条件,因此select结果查询不到数据。

        事务B执行select进行快照读,隔离级别repeatable的读视图产生在事务第一次执行快照读的时间点,因此读取结果和上次分析的一样:{业务字段1:数据1,业务字段2:数据2}

       如果此时启动事务A1、事务B1,明显可以想到,此时产生的读视图和事务A的类似,因此select结果也是查询不到数据。

如有错误,敬请斧正;欢迎转载,但请务必注明出处;最后,在此向神奇的海螺保证,绝不太监!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值