1. redo log和二进制日志的区别
redo log不是二进制日志。虽然二进制日志中也记录了innodb表的很多操作,也能实现重做的功能,但是它们之间有很大区别。
- 二进制日志是在存储引擎的上层产生的,不管是什么存储引擎,对数据库进行了修改都会产生二进制日志。而redo log是innodb层产生的,只记录该存储引擎中表的修改。并且二进制日志先于redo log被记录。
- 二进制日志记录操作的方法是逻辑性的语句。即便它是基于行格式的记录方式,其本质也还是逻辑的SQL设置,如该行记录的每列的值是多少。而redo log是在物理格式上的日志,它记录的是数据库中每个页的修改。
- 二进制日志只在每次事务提交的时候一次性写入缓存中的日志”文件”(对于非事务表的操作,则是每次执行语句成功后就直接写入)。而redo log在数据准备修改前写入缓存中的redo log中,然后才对缓存中的数据执行修改操作;而且保证在发出事务提交指令时,先向缓存中的redo log写入日志,写入完成后才执行提交动作。
- 因为二进制日志只在提交的时候一次性写入,所以二进制日志中的记录方式和提交顺序有关,且一次提交对应一次记录。而redo log中是记录的物理页的修改,redo log文件中同一个事务可能多次记录,最后一个提交的事务记录会覆盖所有未提交的事务记录。例如事务T1,可能在redo log中记录了 T1-1,T1-2,T1-3,T1 共4个操作,其中 T1 表示最后提交时的日志记录,所以对应的数据页最终状态是 T1 对应的操作结果。而且redo log是并发写入的,不同事务之间的不同版本的记录会穿插写入到redo log文件中,例如可能redo log的记录方式如下: T1-1,T1-2,T2-1,T2-2,T2,T1-3,T1* 。
- 事务日志记录的是物理页的情况,它具有幂等性,因此记录日志的方式极其简练。幂等性的意思是多次操作前后状态是一样的,例如新插入一行后又删除该行,前后状态没有变化。而二进制日志记录的是所有影响数据的操作,记录的内容较多。例如插入一行记录一次,删除该行又记录一次。
2. 什么是redo log
我们想象有这么一个生产环境,就是如果我们只在内存的 Buffer Pool 中修改了页面,事务提交后突然发生了某个故障,导致内存中的数据都失效了,那么这个已经提交了的事务对数据库中所做的更改也就跟着丢失了,这你能忍?反正我是不能忍。🙂🙂🙂 这就是持久性遭到了破坏,那我们如何保证持久性呢? 有同学可能说每次事务提交之前把该事务所修改的所有页面都刷新到磁盘。但是,你有没有想过这样做有问题呢?
- 刷新一个完整的数据页太浪费了
有时候我们仅仅修改了某个页面中的一个字节,但是我们知道在 InnoDB 中是以页为单位来进行磁盘IO 的,也就是说我们在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘,我们又知道一个页面默认是 16KB 大小,只修改一个字节就要刷新 16KB 的数据到磁盘上显然是太浪费了
随机 IO 比较慢
一个事务可能包含很多条 SQL 语句,而这些语句可能对 Buffer Pool 中不相邻的数据页进行操作。当把该事务修改过的数据页刷新到磁盘时会产生很多随机 IO,我们知道随机 IO 是比顺序 IO 慢的
我们还记得我们的目的是啥吗?不就是想让已经提交了的事务对数据库中数据所做的修改永久生效,即使后来系统崩溃,在重启后也能把这种修改恢复出来。所以我们其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘,只需要把修改了哪些东西记录一下就好。那用什么东东来记录呢,此时我们的 redo log 就登场了。
redo log 顾名思义,就是重做日志,我们只需要将对数据页做了哪些修改记录到 redo log 就行了,当系统崩溃了,重启之后只要按照 redo log 所记录的步骤重新更新一下数据页,那么该事务对数据库中所做的修改又可以被恢复出来,满足持久性。那为啥用 redo log 呢?
redo 日志占用的空间非常小<br /> 存储表空间ID、页号、偏移量以及需要更新的值所需的存储空间是很小的<br /> redo 日志是顺序写入磁盘的<br /> 在执行事务的过程中,每执行一条语句,就可能产生若干条 redo 日志,这些日志是按照产生的顺序写入磁盘的,也就是使用顺序IO,速度比随机 IO 更快
讲了啥是 redo log,我们现在看看真实的 redo log 到底长啥样吧
redo log格式
redo log 的格式:
type :该条 redo 日志的类型。
space ID :表空间 ID。
page number :页号。
data :该条 redo 日志的具体内容。
这里 redo log 日志的类型比较多,我在这里举两个例子吧:
3. Mini-Transaction 的概念
MySQL 把对底层页面中的一次原子访问的过程称之为一个 Mini-Transaction ,简称 mtr ,比如向某个索引对应的 B+ 树中插入一条记录的过程就是一个 Mini-Transaction 。通过上边的叙述我们也知道,一个所谓的 mtr 可以包含一组 redo 日志,在进行奔溃恢复时这一组 redo 日志作为一个不可分割的整体
一个事务可以包含若干条语句,每一条语句其实是由若干个 mtr 组成,每一个 mtr 又可以包含若干条 redo 日志,画个图表示它们的关系就是这样:
4. redo log的基本概念
redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。
在概念上,innodb通过force log at commit机制实现事务的持久性,即在事务提交的时候,必须先将该事务的所有事务日志写入到磁盘上的redo log file和undo log file中进行持久化。
为了确保每次日志都能写入到事务日志文件中,在每次将log buffer中的日志写入日志文件的过程中都会调用一次操作系统的fsync操作(即fsync()系统调用)。因为MariaDB/MySQL是工作在用户空间的,MariaDB/MySQL的log buffer处于用户空间的内存中。要写入到磁盘上的log file中(redo:ib_logfileN文件,undo:share tablespace或.ibd文件),中间还要经过操作系统内核空间的os buffer,调用fsync()的作用就是将OS buffer中的日志刷到磁盘上的log file中。
也就是说,从redo log buffer写日志到磁盘的redo log file中,过程如下:
5. redo log buffer
为了解决磁盘速度过慢的问题而引入了 Buffer Pool 。同理,写入 redo 日志时也不能直接直接写到磁盘上,实际上在服务器启动时就向操作系统申请了一大片称之为 redo log buffer 的连续内存空间,也叫 redo日志缓冲区 ,简称为 log buffer 。这片内存空间被划分成若干个连续的 redo log block ,如图:
小贴士:我们可以通过启动参数 innodb_log_buffer_size 来指定 log buffer 的大小,在 MySQL 5.7.21 这个版本中,该启动参数的默认值为 16MB 。
MySQL 把 redo 日志放在了大小为 512 字节的页中,但是我们把用来存储 redo 日志的页称为 block , 真正的 redo 日志都是存储到占用 496 字节大小的 log block body 中,图中的 log block header 和 log block tailer 存储的是一些管理信息 。
**LOG_BLOCK_HDR_NO**:每一个 block 都有一个大于 0 的唯一标号,本属性就表示该标号**LOG_BLOCK_HDR_DATA_LEN**:表示 block 中已经使用了多少字节,初始值为 12 (因为 log block body 从第12个字节处开始)。随着往 block 中写入的 redo 日志越来也多,本属性值也跟着增长。如果 log block body 已经被全部写满,那么本属性的值被设置为 512**LOG_BLOCK_FIRST_REC_GROUP**:一条 redo 日志也可以称之为一条 redo 日志记录( redo log record ),一个 mtr 会生产多条 redo 日志记录,这些 redo 日志记录被称之为一个 redo 日志记录组( redo logrecord group )。 LOG_BLOCK_FIRST_REC_GROUP 就代表该 block 中第一个 mtr 生成的 redo 日志记录组的偏移量(其实也就是这个 block 里第一个 mtr 生成的第一条 redo 日志的偏移量)**LOG_BLOCK_CHECKPOINT_NO**:表示所谓的 checkpoint 的序号**LOG_BLOCK_CHECKSUM**:表示 block 的校验值,用于正确性校验
redo日志写入log buffer
向 log buffer 中写入 redo 日志的过程是顺序的,也就是先往前边的block中写,当该block的空闲空间用完之后再往下一个block中写。当我们想往 log buffer 中写入 redo 日志时,第一个遇到的问题就是应该写在哪个 block 的哪个偏移量处,因此 MySQL 提供了一个称之为 buf_free 的全局变量,该变量指明后续写入的 redo 日志应该写入到 log buffer 中的哪个位置,如图所示:
一个 mtr 执行过程中可能产生若干条 redo 日志,这些 redo 日志是一个不可分割的组,所以其实并不是每生成一条 redo 日志,就将其插入到 log buffer 中,而是每个 mtr 运行过程中产生的日志先暂时存到一个地方,当该 mtr 结束的时候,将过程中产生的一组 redo 日志再全部复制到 log buffer 中。我们现在假设有两个名为 T1 、 T2 的事务,每个事务都包含2个 mtr ,我们给这几个 mtr 命名一下:
- 事务 T1 的两个 mtr 分别称为 mtr_T1_1 和 mtr_T1_2 。
- 事务 T2 的两个 mtr 分别称为 mtr_T2_1 和 mtr_T2_2 。
如图所示:
不同的事务可能是并发执行的,所以 T1 、T2 之间的 mtr 可能是交替执行的。每当一个 mtr 执行完成时,伴随该 mtr 生成的一组 redo 日志就需要被复制到 log buffer 中,也就是说不同事务的 mtr 可能是交替写入 log buffer 的,我们画个示意图:
从示意图中我们可以看出来,不同的 mtr 产生的一组 redo 日志占用的存储空间可能不一样,有的 mtr 产生的 redo 日志量很少,比如 mtr_t1_1 、 mtr_t2_1 就被放到同一个block中存储,有的 mtr 产生的 redo 日志量非常大,比如 mtr_t1_2 产生的 redo 日志甚至占用了 3 个 block 来存储
6. redo log buffer刷盘时机
MySQL支持用户自定义在commit时如何将log buffer中的日志刷log file中。这种控制通过变量 **innodb_flush_log_at_trx_commit **的值来决定。该变量有3种值:0、1、2,默认为1。但注意,这个变量只是控制commit动作是否刷新log buffer到磁盘。
- 当设置为1的时候,事务每次提交都会将log buffer中的日志写入os buffer并调用fsync()刷到log file on disk中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。(建议设置为1,不会丢失数据)
- 当设置为0的时候,事务提交时不会将log buffer中日志写入到os buffer,而是每秒写入os buffer并调用fsync()写入到log file on disk中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。
- 当设置为2的时候,每次提交都仅写入到os buffer,然后是每秒调用fsync()将os buffer中的日志写入到log file on disk。

默认情况下事务每次提交的时候都会刷事务日志到磁盘中,这是因为变量 innodb_flush_log_at_trx_commit 的值为1。但是innodb不仅仅只会在有commit动作后才会刷日志到磁盘,这只是innodb存储引擎刷日志的规则之一。 此外,还有一个变量 **innodb_flush_log_at_timeout **的值为1秒,该变量表示的是刷日志的频率 。
规则有如下:
- 发出commit动作时。已经说明过,commit发出后是否刷日志由变量 innodb_flush_log_at_trx_commit 控制。
- 每秒刷一次。这个刷日志的频率由变量 innodb_flush_log_at_timeout 值决定,默认是1秒。要注意,这个刷日志频率和commit动作无关。
- 当log buffer中已经使用的内存超过一半时。
- 当有checkpoint时,checkpoint在一定程度上代表了刷到磁盘时日志所处的LSN位置。
引用:
请阅读此文 :https://blog.csdn.net/weixin_46156200/article/details/121203677 redo(好文)
https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html redo和undo log(好文)
