003_Mysql之事务详解redo log、undolog 和 MVCC

总结

1、不管是redo log 还是 undo log ,都需要在事务提交时写入磁盘文件。不同的加了Log buffer 缓存区,可以将随机读写变为顺序读写。

事务的基础

事务是一组操作的集合,它是不可分割的工作单位,事务会把所有的操作作为一个整体一起向系
统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
image.png
image.png

如上面两张图,原子性、一致性和持久性通过 两份日志来保证;而隔离性则是通过 锁和MVCC 来保证。
所以,理解事务,我们主要理解 redo log 、 undo log 和 MVCC 。

redo log

重做日志,用来记录事务提交时数据页的物理修改,是用来实现事务持久性的。

该日志文件由两部分组成:**重做日志缓冲(redo log buffer)以及重做日志文件(redo log **
file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中, 用
于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用。

没有redo log 时,
在InnoDB引擎中的内存结构中,主要的内存区域就是缓冲池,在缓冲池中缓存了很多的数
据页。 当我们在一个事务中,执行多个增删改的操作时,InnoDB引擎会先操作缓冲池中的数据,如果
缓冲区没有对应的数据,会通过后台线程将磁盘中的数据加载出来,存放在缓冲区中,然后将缓冲池中
的数据修改,修改后的数据页我们称为脏页。 而脏页则会在一定的时机,通过后台线程刷新到磁盘
中,从而保证缓冲区与磁盘的数据一致。 而缓冲区的脏页数据并不是实时刷新的,而是一段时间之后
将缓冲区的数据刷新到磁盘中,假如刷新到磁盘的过程出错了,而提示给用户事务提交成功,而数据却
没有持久化下来,这就出现问题了,没有保证事务的持久性。
image.png

存在 redo log 时

当对缓冲区的数据进行增删改之后,会首先将操作的数据页的变化,**记录在redo **
log buffer中。在事务提交时,会将redo log buffer中的数据刷新到redo log磁盘文件中。
过一段时间之后,如果刷新缓冲区的脏页到磁盘时,发生错误,此时就可以借助于redo log进行数据
恢复,这样就保证了事务的持久性。 而如果脏页成功刷新到磁盘 或 或者涉及到的数据已经落盘,此
时redolog就没有作用了,就可以删除了,所以存在的两个redolog文件是循环写的。

image.png

为什么每一次提交事务,要刷新redo log 到磁盘中呢,而不是直接将buffer pool中的脏页刷新
到磁盘呢 ?
因为在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘。 而redo log在
往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的。顺序写的效率,要远大于随机写。 这
种先写日志的方式,称之为 WAL(Write-Ahead Logging)。

undo log

回滚日志,用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚(保证事务的原子性) 和
MVCC(多版本并发控制) 。

undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo
log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的 update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。

Undo log销毁:
undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC。

Undo log存储:
undo log采用段的方式进行管理和记录,存放在前面介绍的 rollback segment回滚段中,内部包含1024个undo log segment。
在MySQL5.5之前,只支持1个rollback segment,也就是只能记录1024个undo操作。在MySQL5.5之后,可以支持128个rollback segment,分别从resg slot0 - resg slot127,每一个resg slot,也就是每一个回滚段,内部由1024个undo segment 组成,即总共可以记录128 * 1024个undo操作。

undo log 的工作原理
image.png

用undo log实现原子性和持久化的事务的简化过程:
假设有A、B两个数据,值分别为1,2。
A. 事务开始
B. 记录A=1到undo log中
C. 修改A=3
D. 记录B=2到undo log中
E. 修改B=4
F. 将undo log写到磁盘 -------undo log持久化
G. 将数据写到磁盘 -------数据持久化
H. 事务提交 -------提交事务
之所以能同时保证原子性和持久化,是因为以下特点:

更新数据前记录undo log。
为了保证持久性,必须将数据在事务提交前写到磁盘,只要事务成功提交,数据必然已经持久化到磁盘。
undo log必须先于数据持久化到磁盘。如果在G,H之间发生系统崩溃,undo log是完整的,可以用来回滚。
如果在A - F之间发生系统崩溃,因为数据没有持久化到磁盘,所以磁盘上的数据还是保持在事务开始前的状态。

缺陷:每个事务提交前将数据和undo log写入磁盘,这样会导致大量的磁盘IO,因此性能较差。 如果能够将数据缓存一段时间,就能减少IO提高性能,但是这样就会失去事务的持久性。

特别说明:
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。

MVCC版本控制

基本概念

1、当前读

读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select … lock in share mode(共享锁),select … for update、update、insert、delete(排他锁)都是一种当前读。

2、快照读

简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据, 不加锁,是非阻塞读。
• Read Committed(RC 读提交):每次select,都生成一个快照读
• Repeatable Read(RR 可重复读):开启事务后第一个select语句才是快照读的地方。
• Serializable:快照读会退化为当前读。

3、MVCC
全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本, 使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。

4、数据库记录中的三个隐式字段
对于InnoDB存储引擎,每一行记录都有两个隐藏列trx_idroll_pointer,如果表中没有主键和非NULL唯一键时,则还会有第三个隐藏的主键列row_id。 如果有主键,则不会添加该隐藏字段。
image.png

5、版本链
image.png

DB_TRX_ID : 代表最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID,是
自增的。
DB_ROLL_PTR : 由于这条数据是才插入的,没有被更新过,所以该字段值为null。

假设 4个事务,同时操作这张表,会形成下图的版本链。
image.png
image.png

最终我们发现,不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。

6、readview

ReadView(读视图)是 快照读 SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id。
ReadView中包含了四个核心字段:
image.png

而在readview中就规定了版本链数据的访问规则:
trx_id 代表当前undolog版本链对应事务ID。

image.png

MVCC实现原理分析

查询一条记录,基于MVCC 是怎么样的流程
  1. 获取事务自己的版本号,即事务ID
  2. 获取Read View
  3. 查询得到的数据,然后Read View中的事务版本号进行比较。
  4. 如果不符合Read View的可见性规则, 即就需要Undo log中历史快照;
  5. 最后返回符合规则的数据

InnoDB 实现MVCC,是通过Read View+ Undo Log 实现的,Undo Log 保存了历史快照,Read View可见性规则帮助判断当前版本的数据是否可见。

读已提交(RC)隔离级别,存在不可重复读问题的分析历程

前情提要:
每次select,都生成一个快照读
1、创建core_user表,插入一条初始化数据,如下:

2、隔离级别设置为读已提交(RC),事务A和事务B同时对core_user表进行查询和修改操作。
事务A: select * fom core_user where id=1 事务B: update core_user set name =”曹操”
执行流程如下:

最后事务A查询到的结果是,name=曹操的记录,我们基于MVCC,来分析一下执行流程:
(1). A开启事务,首先得到一个事务ID为100
(2).B开启事务,得到事务ID为101
(3).事务A生成一个Read View,read view对应的值如下
然后回到版本链:开始从版本链中挑选可见的记录:

版本链
由图可以看出,最新版本的列name的内容是孙权,该版本的trx_id值为100。开始执行read view可见性规则校验:
min_limit_id(100)=<trx_id(100)<102; creator_trx_id = trx_id =100;
由此可得,trx_id=100的这个记录,当前事务是可见的。所以查到是name为孙权的记录。
(4). 事务B进行修改操作,把名字改为曹操。把原数据拷贝到undo log,然后对数据进行修改,标记事务ID和上一个数据版本在undo log的地址。

(5) 提交事务
(6) 事务A再次执行查询操作,新生成一个Read View,Read View对应的值如下
然后再次回到版本链:从版本链中挑选可见的记录:

从图可得,最新版本的列name的内容是曹操,该版本的trx_id值为101。开始执行Read View可见性规则校验:
min_trx_id(100)=<trx_id(101)<max_trx_id(102);
因为事务已经提交不属于活跃的的事务ID,所以,trx_id=101,不属于m_ids集合,
但是,trx_id=101这个记录,对于当前事务是可见的。所以SQL查询到的是name为曹操的记录。
综上所述,在读已提交(RC)隔离级别下,同一个事务里,两个相同的查询,读取同一条记录(id=1),却返回了不同的数据(第一次查出来是孙权,第二次查出来是曹操那条记录),因此RC隔离级别,存在不可重复读并发问题。

可重复读(RR)隔离级别,解决不可重复读问题的分析

RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。 而RR 是可重复读,在一个事务中,执行两次相同的select语句,查询到的结果是一样的。

那MySQL是如何做到可重复读的呢? 我们简单分析一下就知道了我们看到,在RR隔离级别下,只是在事务中第一次快照读时生成ReadView,后续都是复用该 ReadView,那么既然ReadView都一样, ReadView的版本链匹配规则也一样, 那么最终快照读返回的结果也是一样的。

所以呢,MVCC的实现原理就是通过 InnoDB表的隐藏字段、UndoLog 版本链、ReadView来实现的。
而MVCC + 锁,则实现了事务的隔离性。 而一致性则是由redolog 与 undolog保证。

参考文档:
https://blog.csdn.net/Weixiaohuai/article/details/117867353
https://zhuanlan.zhihu.com/p/421769708


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值