mysql_redo_log_buffer

news2024/12/22 10:54:23

Redo Log

MySQL 的 InnoDB 存储引擎使用 Redo Log 记录系统中每个事务的修改,从而在系统崩溃重启时能够把系统恢复到崩溃时的状态。因此,Redo Log 用于保证事务的持久性,即一旦某个事务成功提交,即使系统发生了崩溃,那么在系统重启后也能看到这个事务的修改。

alt

Redo Log 始于 mini-transaction (mtr),止于磁盘文件:

  • 一个事务在修改数据页时会开启一个 mtr,对数据页的修改会以 Redo Record 的形式暂存在该 mtr 中;
  • 当提交 mtr 时,会把其中的 Redo Record 复制到 Log Buffer 中形成 Redo Log;
  • log writer 线程负责把 Log Buffer 中的 Redo Log 写入操作系统内核的 Page Cache;
  • log flusher 线程负责把 Page Cache 中的 Redo Log 写入磁盘文件 (Redo File)。

Log Sequence Number (LSN) 用于标记 Redo Log:

  • last_checkpoint_lsn 标识最近一次检查点的位置。在 last_checkpoint_lsn 前的 Redo Log 对应的脏页已被写入磁盘,因此在 last_checkpoint_lsn 前的 Redo Log 可以被回收。 last_checkpoint_lsn 可以看作 Redo Log 的开头;
  • flushed_to_disk_lsn 标识已落盘的 Redo Log 的位置。在 flushed_to_disk_lsn 前的 Redo Log 已被写入磁盘,这些 Redo Log 不会因为系统掉电而丢失;
  • write_lsn 标识已写入 Page Cache 的 Redo Log 的位置。在 write_lsn 前的 Redo Log 已被写入操作系统缓存,这些 Redo Log 不会因为数据库意外退出而丢失,操作系统会继续把这些 Redo Log 写入磁盘;
  • current_lsn 标识最新的 Redo Log 的位置。已有的 Redo Log 的 LSN 不会超过 current_lsncurrent_lsn 可以看作 Redo Log 的末尾。

由此可见,完整的 Redo Log 包括三部分:

  • last_checkpoint_lsn ~ flushed_to_disk_lsn:这些 Redo Log 位于磁盘中,除非磁盘损坏,否则不会丢失;
  • write_lsn ~ flushed_to_disk_lsn:这些 Redo Log 位于 Page Cache 中,除非系统掉电,否则不会丢失;
  • current_lsn ~ write_lsn:这些 Redo Log 位于用户空间缓冲区中,除非数据库意外退出,否则不会丢失。

上述 LSN 的关系为 (左右滑动查看完整公式):

Log Buffer

Log Buffer 存放用户空间缓冲区的 Redo Log。Log Buffer 以 Redo Block 为单位组织 Redo Record。一个 Redo Block 的大小固定为 512 字节,包括 12 字节的头部和 4 字节的尾部,剩下的 496 字节留给 Redo Record。在为 Redo Log 分配 LSN 时会纳入 Redo Record 以及每个 Redo Block 的头部和尾部。由于 mtr 只记录 Redo Record,不记录 Redo Block 的头部和尾部,因此 Sequence Number (SN) 单独用于标记 Redo Record。由于每个 Redo Block 都是固定大小,因此 SN 和 LSN 的转换十分简单,通过 log_translate_sn_to_lsn() 和 log_translate_lsn_to_sn() 函数可以实现 SN 和 LSN 间的互相转换。

Log Buffer 的逻辑起点和逻辑终点分别是 write_lsncurrent_lsn。在 write_lsn 前的 Redo Log 已写入 Page Cache 或磁盘文件,current_lsn 是最新的 Redo Log 的位置。实际实现中维护的是 log.sn,它标识最新的 SN,在 log_get_lsn() 函数中通过调用 log_translate_sn_to_lsn() 函数把 log.sn 转换成 current_lsn

用户线程提交 mtr 时写 Log Buffer 的流程如下:

  1. 在 Log Buffer 中申请一段连续的空闲空间,这段空间的大小与该 mtr 本次要写的 Redo Log 长度相等;
  2. 把 mtr 中的 Redo Record 复制到申请的空闲空间中,复制时留出每个 Redo Block 的头部和尾部,log writer 线程负责填入每个 Redo Block 的头部和尾部

其中,第 1 步必须串行执行,第 2 步可以并行执行。在实现上,Log Buffer 是一个循环队列,采用无锁化设计允许多个 mtr 同时写 Log Buffer,提高了写 Redo Log 的速率。

alt

:只有一个例外,每个 Redo Block 头部中的 first_rec_group 字段由提交 mtr 的用户线程负责填入。

申请空闲空间

提交 mtr 的用户线程先调用 prepare_write() 函数算出全部 Redo Record 的长度,再调用 log_buffer_reserve() 函数向 Log Buffer 申请一段连续的空闲空间:

  1. 调用 log_buffer_s_lock_enter_reserve() 函数获得当前的 SN 值作为起始 SN,加上 Redo Record 的长度后得到终止 SN;
  2. 调用 log_translate_sn_to_lsn() 函数基于起始 SN 和终止 SN 计算得到起始 LSN 和终止 LSN,此时纳入了 Redo Block 的头部和尾部,本次 Redo Log 的长度从起始 LSN 到终止 LSN;
  3. 若 Redo Log 的长度超出 Log Buffer 的空闲空间大小 ,则调用 log_wait_for_space_after_reserving() 函数使用户线程等待 log writer 线程把 Log Buffer 中较老的 Redo Log 写入 Page Cache;
  4. 若 Redo File 中的空闲空间不足,则调用 log_writer_wait_on_checkpoint() 函数使 log writer 线程等待 page cleaner 线程刷脏页以及等待 log checkpoint 线程执行检查点来清除 Redo File 中较老的 Redo Log;
  5. 申请空闲空间成功。

:由于 write_lsn 是 Log Buffer 的逻辑起点,因此只需比较终止 LSN 是否大于 write_lsn 加上 Log Buffer 的容量 (实际会再减去两块 Redo Block 的大小,参见 log_update_buf_limit() 函数)。

复制 Redo Record

mtr 用一个链表 (mtr.m_impl->m_log.m_list) 暂存 Redo Record。该链表由若干个 Block 组成,每个 Block 存储若干条 Redo Record,只有当前一个 Block 放满后才会动态创建后一个 Block。一个 mtr 中所有的 Redo Record 组成一组,这组 Redo Record 在崩溃恢复时要么全部重做,要么全部不做 (即,原子性)。Redo Block 头部的 first_rec_group 字段用于标识该 Redo Block 中第一组的起始位置,该字段由用户线程在提交 mtr 时填入 (log writer 线程并不知道哪些 Redo Record 属于同一组,无法填入 first_rec_group 字段)。

alt

申请空闲空间成功后,用户线程调用 log_buffer_write() 函数把 mtr 中的 Log Record 复制到 Log Buffer 中:

  1. 从 mtr 中的第一个 Block 开始,依次把 Block 中的 Redo Record 复制到 Log Buffer 中,复制时留出每个 Redo Block 的头部和尾部;
  2. 处理完所有 Block 后,若 mtr 中的第一条 Redo Record 和最后一条 Redo Record 不在同一个 Redo Block 中,则调用 log_buffer_set_first_record_group() 函数填入最后一条 Redo Record 所在的 Redo Block 头部的 first_rec_group 字段。字段值为终止 LSN 对 Redo Block 大小取模,即下一组在该 Redo Block 中的起始位置。

Recent Written Buffer

用户线程把 mtr 中的 Redo Record 复制到 Log Buffer 中后,由 log writter 线程负责把 Log Buffer 中的 Redo Log 写入 Page Cache。由于多个用户线程可以并行地写 Log Buffer,因此存在一些用户线程已经复制完 Redo Record,另一些用户线程还没复制完 Redo Record,这会使 Log Buffer 中出现“空洞”。下图中 都已经复制完 Redo Record,但 还没复制完 Redo Record, 对应的位置就是 Log Buffer 中的“空洞”。因此,还需要追踪 Log Buffer 中从 write_lsn 到哪个位置的 Redo Log 是连续的,从而避免 log writer 线程越过“空洞” (即,漏写“空洞”对应的 Redo Log)。

alt

Recent Written Buffer 负责追踪用户线程写 Log Buffer 的进度。Recent Written Buffer 的逻辑起点是 buf_ready_for_write_lsn,在 buf_ready_for_write_lsn 前的 Redo Log 已经存在 Log Buffer 中。可以看出 (左右滑动查看完整公式):

每把一个 Block 中的 Redo Record 复制到 Log Buffer 后,就在 Recent Written Buffer 中插入一条链接 (Link)。以上图中的 为例, 使用了三个 Block 暂存 Redo Record。对于每个 Block,先调用 log_buffer_write() 函数把该 Block 中的 Redo Record 复制到 Log Buffer 中,再调用 log_buffer_write_completed() 函数在 Recent Written Buffer 中插入一条链接,链接的起点和终点分别是该 Block 的起始 LSN 和终止 LSN。若 buf_ready_for_write_lsn 和一条链接的起点重合,则将 buf_ready_for_write_lsn 向前移动到该链接的终点。

具体地,调用 log_buffer_write_completed() 函数在 Recent Written Buffer 中插入一条链接的流程如下:

  1. 检查 Recent Written Buffer 中是否有足够的空闲空间 。若空闲空间不足,则等待其他用户线程复制完 Redo Record 并且等待 buf_ready_for_write_lsn 向前移动;
  2. 调用 log.recent_written.add_link_advance_tail() 函数根据起始 LSN 和终止 LSN 向 Recent Written Buffer 中插入一条链接。首先令起始 LSN 对 Recent Written Buffer 的容量取模,得到起始 LSN 在 Recent Written Buffer 中的位置,然后在该位置填入终止 LSN;
  3. 用户线程向前移动 buf_ready_for_write_lsn,直到遇到“空洞”或终止 LSN 为止。

由此可知:

  • mtr 每把一个 Block 的 Redo Record 复制到 Log Buffer 中后,就在 Recent Written Buffer 中加入一条链接;
  • 只要 Recent Written Buffer 中出现链接,就意味着这条链接对应的 Redo Log 已经存在 Log Buffer 中;
  • log writer 线程和用户线程都能向前移动 buf_ready_for_write_lsn。log writer 线程每次把从 write_lsnbuf_ready_for_write_lsn 的 Redo Log 写入 Page Cache,并把 write_lsn 设为 buf_ready_for_write_lsn

:由于 buf_ready_for_write_lsn 是 Recent Written Buffer 的逻辑起点,因此只需比较 buf_ready_for_write_lsn 加上 Recent Written Buffer 的容量是否大于起始 LSN (参见 log.recent_written.has_space() 函数)。

Recent Closed Buffer

用户线程最后还要把脏页加入到 Buffer Pool 的 flush list 中。mtr 使用一个链表 (mtr.m_impl->m_memo.m_list) 记录本次修改到的数据页,这些数据页要么是第一次变脏 (由本次 mtr 修改),要么已经在 flush list 中 (由较早 mtr 修改后,还没来得及落盘)。

Recent Closed Buffer 负责追踪 mtr 中的脏页被加入到 flush list 中的进度。Recent Closed Buffer 的逻辑起点是 buf_dirty_pages_added_up_to_lsn,在 buf_dirty_pages_added_up_to_lsn 前的 Redo Log 对应的脏页已经存在 flush list 中。与 buf_ready_for_write_lsn 一样,buf_dirty_pages_added_up_to_lsn 也是通过链接向前移动。

在处理完所有 mtr 中的 Block 之后,用户线程一次性标记脏页并把所有新的脏页加入到 flush list 中:

  1. 调用 log_wait_for_space_in_log_recent_closed() 函数检查 Recent Closed Buffer 中是否有足够的空闲空间 。若空闲空间不足,则等待其他用户线程把脏页加入到 flush list 中并且等待 buf_dirty_pages_added_up_to_lsn 向前移动;
  2. 调用 add_dirty_page_to_flush_list() 函数标记脏页并把新的脏页加入到 flush list 中。对于每一个数据页,调用 buf_flush_note_modification() 函数把数据页的 newest modification 置为终止 LSN。对于第一次变脏的数据页,调用 buf_flush_insert_into_flush_list() 函数把数据页的 oldest modification 置为起始 LSN,并把该数据页加入到 flush list 中;
  3. 调用 log_buffer_close() 函数根据起始 LSN 和终止 LSN 向 Recent Closed Buffer 中插入一条链接。首先令起始 LSN 对 Recent Closed Buffer 的容量取模,得到起始 LSN 在 Recent Closed Buffer 中的位置,然后在该位置填入终止 LSN;
  4. 用户线程向前移动 buf_dirty_pages_added_up_to_lsn,直到遇到“空洞”或终止 LSN 为止。

:由于 buf_dirty_pages_added_up_to_lsn 是 Recent Closed Buffer 的逻辑起点,因此只需比较 buf_dirty_pages_added_up_to_lsn 加上 Recent Closed Buffer 的容量是否大于起始 LSN (参见 log.recent_closed.has_space() 函数)。

Last Checkpoint LSN

为使 Log File 留有足够的空闲空间,需要及时把脏页刷盘并向前移动 last_checkpoint_lsn。更新 last_checkpoint_lsn 需借助 flush list 和 Recent Closed Buffer。

已知 flush list 存放着等待落盘的脏页,并且每个脏页的 oldest modification 记录着该数据页变脏时的 LSN,那么能否把 last_checkpoint_lsn 置为 flush list 中最小的 oldest modification?答案是不能。以下图为例, 已经把脏页加入到 flush list 中, 还在复制 Redo Record 到 Log Buffer 中,其脏页还没加入到 flush list 中, 的脏页已经刷盘。此时 flush list 中最小的 oldest modification 的起始 LSN,如果把 last_checkpoint_lsn 置为 的起始 LSN,那么意味着 的脏页也已落盘,但实际上 的脏页还未落盘!

alt

当 Recent Closed Buffer 中存在“空洞”时,flush list 并未包含所有脏页,需借助 Recent Closed Buffer 追踪那些还未加入 flush list 的脏页。buf_dirty_pages_added_up_to_lsn 之前的 Redo Log 对应的脏页已经加入到 flush list 中;buf_dirty_pages_added_up_to_lsn 之后可能存在“空洞”,“空洞”对应的脏页还未加入到 flush list 中;“空洞”之后可能存在链接,链接对应的脏页已经加入到 flush list 中。因此,只需取 flush list 中最小的 oldest modificationbuf_dirty_pages_added_up_to_lsn 二者中的较小值,就能确保该值之前的 Redo Log 对应的脏页已经落盘。

实际的计算方法更复杂,log_compute_available_for_checkpoint_lsn() 函数负责计算 available_for_checkpoint_lsn 用于标识下一次执行检查点的位置。available_for_checkpoint_lsn 被设为 flush list 中最小的 oldest modification 减去 Recent Closed Buffer 的容量 buf_dirty_pages_added_up_to_lsn 以及 flushed_to_disk_lsn 三者中的最小值。log checkpoint 线程每次执行检查点时把 last_checkpoint_lsn 推进到 available_for_checkpoint_lsn

:为了兼顾性能,实际并不会遍历 flush list 中的所有脏页,得到的只是最小的 oldest modification 的近似值 (参见 buf_pool_get_oldest_modification_lwm() 函数)。

alt

欢迎关注微信公众号 fightingZh

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2119495.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#开发移动应用系列(4.调用系统应用,以及第三方应用(调用与被调用))

写完这篇..本系列基本也就结束了. 看完这些,基本做个Webapp应该是毫无问题的了..其实略微有点伤感,Xamarin确实是好东西.. 奈何,生不逢时..等完善了. 开源了..社区化了..APP应用(指的是安装包类客户端)已经逐渐没落了.. 算了,话不多说.开始正文 确定一下本篇的学习目标: 1.…

【MySQL】—— MySQL命令行客户端介绍

目录 (一)mysql客户端简介 (二)mysql客户端选项 2.1 指定选项的方式 2.2 mysql 客户端命令常用选项 2.3 在命令行中使用选项 (三)选项(配置)文件 3.1 使用方法 3.2 选项文件位置及加载顺序 3.2.1…

mysql——关于表的增删改查(CRUD)

目录 比较运算符和逻辑运算符图 一、增加(Create) 1、全列插入 2、指定列插入 二、查询(Retrieve) 1、全列查询 2、指定列查询 3、别名(as) 4、表达式查询 5、去重(distinct) 6、…

全网最适合入门的面向对象编程教程:46 Python函数方法与接口-函数与事件驱动框架

全网最适合入门的面向对象编程教程:46 Python 函数方法与接口-函数与事件驱动框架 摘要: 函数是 Python 中的一等公民,是一种可重用的代码块,用于封装特定的逻辑;事件驱动框架是一种编程模式,它将程序的控制流转移给外部事件,如用…

Simulink库模块作用及简单应用(一)

01--Data Store Memory模块 数据存储模块一般是和数据写入还有数据访问一同搭配使用的 可以从帮助文档看到该模型如下的关键使用信息: Data Store Memory 模块定义并初始化一个命名的共享数据存储,即一个内存区域,供指定相同数据存储名称的…

客户端负载均衡Ribbon 小实例

文章目录 一,概述二,实现过程三,项目源码1. 源码放送:2. 部署方式 四,功能演示五,其他 一,概述 一般来说,提到负载均衡,大家一般很容易想到浏览器 -> NGINX -> 反…

Java 每日一刊(第一期):Java 的历史

文章目录 Java 的起源与诞生Java 的早期发展(1995-2000)Java 的转型与扩展(2000-2010)Oracle 时代的 Java(2010-至今)本期小知识 Java 的起源与诞生 Java 的历史可以追溯到 20 世纪 90 年代,由 …

【数据结构(初阶)】——二叉树

【数据结构】——二叉树 文章目录 【数据结构】——二叉树前言1. 树的概念及结构1.1 树的概念1.2 树的结构 2. 二叉树的概念及结构2.1 二叉树的概念2.2 二叉树的结构2.3 二叉树的性质 3. 二叉树顺序结构及概念3.1 二叉树的顺序结构3.2 堆的概念及结构3.3 堆的实现3.3.1 堆的基本…

【C++ Qt day9】

2、将day1做的登录界面升级优化【资源文件的添加】 3、 使用手动连接,将登录框中的取消按钮使用第2种方式的连接到自定义的槽函数中,在自定义的槽函数中调用关闭函数 将登录按钮使用qt4版本的连接到自定义的槽函数中,在槽函数中判断ui界面上…

黑马点评16——多级缓存-JVM进程缓存

文章目录 什么是多级缓存导入商品案例初识Caffeine实现进程缓存 什么是多级缓存 但是现在的nginx的压力太大了,所以nginx也要部署成集群 当然我们的redis、tomcat都可以部署成集群 导入商品案例 我们在docker中开启了一个mysql的数据库,里面配置了一个…

C和指针:高级指针话题

进一步探讨指向指针的指针 int i; int *pi; int **ppi; 这些声明在内存中创建了下列变量。如果它们是自动变量,无法猜测它们的初始值。 二级指针指向一级指针 ppiπ *ppi&i; ia; *pia; **ppia; 为什么要使用指针? 因为函数传参使用值传递不会…

[C#学习笔记]接口的特性与用法

视频地址&#xff1a;一期视频看透C#接口的全部特性及用法_哔哩哔哩_bilibili 强烈推荐学习C#和WPF的朋友关注此UP&#xff0c;知识点巨多&#xff0c;讲解透彻&#xff01; 一、总览 public interface IOverall {/// <summary>/// 最普通的方法/// </summary>v…

[数据集][目标检测]打电话检测数据集VOC+YOLO格式8985张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;8985 标注数量(xml文件个数)&#xff1a;8985 标注数量(txt文件个数)&#xff1a;8985 标注…

【C++】C++入门基础,详细介绍命名空间,缺省参数,函数重载,引用,内联函数等

目录 1. 命名空间 1.1 使用命名空间的目的 1.2 命名空间定义 1.3 命名空间使用 2. 缺省参数 2.1 缺省参数概念 2.2 缺省参数分类 2.3 实际案例 2.4 注意事项 3. 函数重载 3.1 函数重载概念 3.2 函数重载原理 4. 引用 4.1 引用的概念 4.2 引用的特性 4.3 使用…

JavaScript案例---求质数

n等于19&#xff0c;是质数 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"wid…

OpenAI Gymnasium, are there any libraries with algorithms supporting it?

题意&#xff1a;对于OpenAI Gym&#xff0c;是否有支持它的算法库&#xff1f; 问题背景&#xff1a; OpenAI has released a new library called Gymnasium which is supposed to replace the Gym library. There are many libraries with implamentations of RL algorithms…

机械学习—零基础学习日志(Python做数据分析02)

现在开始使用Python尝试做数据分析。具体参考的网址链接放在了文章末尾。 引言 我通过学习《利用Python进行数据分析》这本书来尝试使用Python做数据分析。书里让下载&#xff0c;anaconda&#xff0c;使用Jupyter来写代码&#xff0c;只是下载一个anaconda的确有点费时间&am…

RabbitMQ 04 集群,用于提高系统性能

01.背景 02.单个节点的MQ会持久化的记录什么数据 03.集群情况下的MQ会持久化的记录什么数据 04.集群中的队列 单个节点的队列&#xff1a; 集群的队列&#xff1a; 05. 两个原因&#xff1a; 这样做带来的好处&#xff1a; 05.集群的交换机 交换机的本质 交换机在集…

Unity TextMeshPro 设置竖排

默认竖排是这样的 但是我们要的竖排效果并不是这样我们要是竖排连续的根据文本限制来进行换行 第一步我们先设置文本的旋转Z轴为90如下图 然后我们给文本加一个Tag <rotate270> 如下图 但是这个效果还是不是我们想要的效果我们可以使用TexeMeshPro提供的一个选项EnableR…

97.游戏的启动与多开-共享内存多开检测

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 上一个内容&#xff1a;96.游戏的启动与多开-窗口多开检测与破解 以 96.游戏的启动与多开-窗口多开检测与破解 …