您当前的位置: 首页 > 

Linux小百科

暂无认证

  • 0浏览

    0关注

    1185博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Redo 日志从产生到写入日志文件的全过程

Linux小百科 发布时间:2022-06-30 10:10:21 ,浏览量:0

MySQL 8.0 以前,Redo 日志是串行写入 log buffer 的,多个用户线程想要同时往 log buffer 里写日志,那是不行的,必须排队等待(获取互斥锁),拿到互斥锁之后,才能往 log buffer 里写日志。

对于这样的剧情,想必大家不会陌生:美国大片中拯救世界的英雄,平时看起来跟普通人没啥区别,甚至还可能会有点让人看不上。

但是,关键时刻,却能爆发出惊人能量,挽狂澜于既倒,扶大厦于将倾,拯救世界于危难之中。

今天我们要聊的主角:Redo 日志,也是这样的平民英雄。

本来 InnoDB 接收到插入、修改、删除这样的 DML 语句,以及创建表 & 索引、修改表结构这样的 DDL 语句,修改 Buffer Pool 中的数据页之后就完事了。

因为要保证数据不丢失,事情就变的复杂了,修改了数据页不算完,还要生成 Redo 日志,生成了也不算完,还要把它写入 Redo 日志文件。

为了方便描述,本文后面会把 Redo 日志文件简称为日志文件。

通过以上描述,相信大家能够发现,生成 Redo 日志并写入日志文件,显然是额外操作,会额外消耗资源。

不惜额外消耗宝贵的服务器资源都要保存下来的东西,肯定不能是个绣花枕头,那这个有用的枕头什么时候能派上用场呢?

当然是服务器累了想小憩一下(突然崩溃)的时候了 ^_^。

服务器也不容易,谁还没有个突然崩溃的时候呢?

说了这么多,是时候确定 Redo 日志的历史地位了:Redo 日志,在太平日子里,不但是个鸡肋,更是个累赘,但是,别把它不当英雄,关键时刻还得靠它拯救数据库。

饭前甜点到此为止,接下来是正餐。

本文内容基于 MySQL 8.0.29 源码。

正文

1、概述

MySQL 8.0 以前,Redo 日志是串行写入 log buffer 的,多个用户线程想要同时往 log buffer 里写日志,那是不行的,必须排队等待(获取互斥锁),拿到互斥锁之后,才能往 log buffer 里写日志。

MySQL 8.0 中,串行写入变为并行写入,log buffer 由乡间小道变成了单向 8 车道的高速公路,多个用户线程可以同时往 log buffer 里写入 Redo 日志,效率大大提升。

Redo 日志从产生到刷盘,一共会经历 4 个阶段(产生、写 log buffer、写日志文件、刷盘),本文会用 4 个小节分别介绍这 4 个阶段。

2、Redo 日志产生

以一条非常简单的插入语句为例,这个语句包含自增列,并且只插入一条记录,我们假设插入过程中不会造成索引页分裂,也不会产生溢出页。

不考虑 Undo 日志产生的 Redo 日志,这样一条 SQL 语句会包含 2 条 Redo 日志(这 2 条日志会形成一个日志组):

  • 一条日志中保存着表中自增列的最大值(MySQL 8.0 把自增列的值持久化了)。
  • 另一条日志中保存着插入记录各字段的值。

每条日志中还有可能会包含 InnoDB 需要的其它信息。

插入记录的过程中,会先产生一条 Redo 日志用于记录表中自增列的最大值,然后插入记录,再产生另一条 Redo 日志。

Redo 日志并不会每产生一条就马上写入 log buffer,而是一组 Redo 日志攒到一起往 log buffer 里写。

问题来了,产生了一条 Redo 日志不能马上写入 log buffer,那怎么办?

那就需要有一个地方临时存放日志组中不同时间点产生的日志了,这个地方就是 mtr 中的 m_log 链表。

m_log 链表是由一个一个 block 组成的链表,block 大小为 512 字节,每产生一条日志,就追加到 m_log 的 block 中,如果一个 block 写满了,就再申请一个 block 接着写。

那 mtr 又是个啥?

mtr 是 Mini-Transaction 的缩写,是一组不可分隔的操作组成的一个整体,就像前面插入语句的例子中,保存表中自增列的最大值和插入记录就是一组不可分隔的操作,必须放入一个 mtr。

两个操作放入一个 mtr,它们的日志也就放在同一个 mtr 中了。这样就能保证两个操作产生的 Redo 日志一起写入 log buffer 和日志文件中。

mtr 的用途可不止打包一组 Redo 日志这么简单,它还会对 SQL 执行过程中 mtr 需要访问的 Buffer Pool 中的页加锁、修改页中的数据、释放锁,本文我们只介绍 Redo 日志,对于 mtr 就不再展开了。

还有一个概念需要解释一下,日志组就是一个 mtr 中的所有日志。

3、写入 log buffer

mtr 中一组不可分隔的操作都完成之后,就该提交了,mtr 提交过程中要干的第一件事就是把它里面临时存放的一组 Redo 日志写入到 log buffer 中。

一个事务中可能会包含多个 mtr,mtr 的提交和事务的提交不是一个概念,不要混淆。

前面说到在 MySQL 8.0 中,往 log buffer 里写日志不需要排队等待(获取互斥锁),多个用户线程可以同时写入。

这个无锁化设计是通过在 log buffer 中为每个 mtr 中的 Redo 日志预留空间实现的,每个 mtr 都有一段属于自己的空间,各自往自己专属的空间内写入日志,相互之间就不影响了。

用户线程的 mtr 往 log buffer 写 Redo 日志前,会先获取一段序列号。

以当前系统中已产生的最大序列号(SN)作为 start_sn,加上本次要往 log buffer 中写入的 Redo 日志的字节数(len),得到 end_sn(end_sn = start_sn + len)。

start_sn ~ end_sn 就是本次要写入 log buffer 的 Redo 日志的序列号区间。

获取 start_sn、end_sn 的过程是原子操作,多个线程之间不会出现冲突,不会获取到有交叉的序列号区间。

拿到 start_sn ~ end_sn 只是第一步,还需要进行一次转换,把序列号(SN)转换为日志序列号(LSN),得到一个 LSN 的范围:start_lsn ~ end_lsn,这个范围对应着 log_buffer 中为 mtr 即将写入的 Redo 日志预留的空间。

SN 是截止某个时刻,InnoDB 中实际产生的 Redo 日志字节数。

SN 按照 496 字节拆分,拆分后每 496 字节,加上 12 字节的头信息、4 字节尾部检验码,得到 512 字节的 block,经过这样的转换之后,得到的数字就是 LSN。

至此,写入日志到 log buffer 的准备工作又往前推进了一步。

但是,别着急,也许还要再等等,如果 log buffer 中剩余空间不够写入当前 mtr 的 Redo 日志,那就需要等到 log buffer 中的 Redo 日志被写入日志文件,为当前 mtr 的 Redo 日志腾出空间才行。

这里的写入日志文件,只是调用了操作系统的写文件方法,把 Redo 日志写入日志文件的操作系统缓冲区中,日志文件暂时还不会刷新到磁盘上。

那怎么判断 log buffer 中是否有空间呢?

要回答这个问题,我们需要先介绍一个属性 log_sys.write_lsn,表示 LSN 小于 log_sys.writen_lsn 的日志都已经写入到日志文件缓冲区中。

end_sn

关注
打赏
1665632672
查看更多评论
立即登录/注册

微信扫码登录

0.2265s