redo log 与 binlog 区别
| redo log | binlog | |
|---|---|---|
| 基本概念 | 重做日志,是物理日志,记录了那个页上的数据做了什么修改 | 二进制日志,是逻辑日志,记录的是对数据库的所有修改操作 |
| 文件大小 | redo log 的大小是固定的。 | binlog 可通过配置参数 max_binlog_size 设置每个 binlog 文件的大小。 |
| 实现方式 | redo log 是 InnoDB 引擎层实现的,并不是所有引擎都有。 | binlog 是 Server 层实现的,所有引擎都可以使用 binlog 日志 |
| 记录方式 | redo log 采用循环写的方式记录,当写到结尾时,会回到开头循环写日志。 | binlog通过追加的方式记录,当文件大小大于给定值后,后续的日志会记录到新的文件上 |
| 适用场景 | redo log 适用于崩溃恢复(crash-safe) | binlog 适用于主从复制和数据恢复 |
由 binlog 和 redo log 的区别可知: binlog 日志只用于归档,只依靠 binlog 是没有crash-safe能力的。但只有 redo log 也不行,因为 redo log 是 InnoDB 特有的,且日志上的记录落盘后会被覆盖掉。因此需要 binlog 和 redo log 二者同时记录,才能保证当数据库发生宕机重启时,数据不会丢失。
2.1 Redo Log
redo log(重做日志):凡是修改页面的地方,都需要记录相应的 redo 日志
2.1.1 Redo Log 基本概念
Redo Log 也被称作重做日志,它是在 InnoDB 存储引擎中产生的,用来保证事务的原子性和持久性,它确保 MySQL 事务提交后,事务所涉及的所有操作要么全部执行成功,要么全部执行失败。Redo Log 主要记录的是物理日志,也就是对磁盘上的数据修改操作进行记录。比方说某个事务将系统表空间中的第100号页面中偏移量为1000处的那个字节的值1改成2我们只需要记录一下:
将第0号表空间的100号页面的偏移量为1000处的值更新为2。
Redo Log 往往用来恢复提交后的物理数据页,不过只能恢复到最后一次提交的位置。Redo Log 通常包含两部分:
- 存放在内存中的日志缓冲,称作 Redo Log Buffer,这部日志比较容易丢失;
- 存放在磁盘上的重做日志文件,称作 Redo Log File,这部分日志是持久化到磁盘的,不容易丢失
2.1.2 Redo Log 基本原理
Redo Log 能够保证事务的原子性和持久性,在 MySQL 发生故障时,尽力避免内存中的脏页数据写入数据表的 ibd 文件。在重后MySQL服务时,可以根据 Redo Log 恢复事务已经提交但是还未写入 ibd 文件中的数据,从而对事务提交的数据进行持久化操作。例如,在商城系统的下单业务中,用户提交订单时,系统会创建一条新的订单记录并保存到订单数据表中.在MySQL内部, Redo Log 的基本原理可以使用图2-1表示。
从图中可以看出,用户下单后系统创建订单记录, MySQL 在提交事务时,会将数据写入Redo Log Buffer,而Redo Log Buffer 中的数据会根据一定的规则写入 Redo Log 文件,具体规则将下面介绍。当MySQL发生故障重启时,会通过 Redo Log 中的数据对订单表中的数据进行恢复,也就是将 Redo Log 文件中的数据恢复到order.ibd 文件中。系统可以根据需要,查询并加载订单表中的数据(也就是加载 order.ibd 文件中的数据),也可以向订单表写入数据(也就是持久化数据到 order.ibd 文件中)。数据页持久化后,是保存在 ibdata1(没有开启
innodb_file_per_table时的共享表空间文件)或者 .ibd(开启innodb_file_per_table时)文件中的
2.1.3 Redo Log 存储方式
InnoDB 的 Redo Log 是固定大小的,比如可以配置为一组4个文件,每个文件的大小是1GB,那么总共就可以记录4GB的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示 ib_logfileN。
write pos 是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。write pos 和 checkpoint之间的是还空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示写满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。
2.1.4 写入 Redo Log
2.1.4.1 Mini-Transaction概念
对底层页面中的一次原子访问的过程称之为一个Mini-Transaction,简称mtr,比如
- 修改一次
Max Row ID的值算是一个Mini-Transaction; - 向某个索引(聚簇或二级)对应的
B+树中插入一条记录的过程也算是一个Mini-Transaction; - 还有其他的一些对页面的访问操作。。。
一个所谓的mtr可以包含一组redo日志,在进行崩溃恢复时这一组redo日志作为一个不可分割的整体。一个事务可以包含若干条语句,每一条语句其实是由若干个mtr组成,每一个mtr又可以包含若干条redo日志,画个图表示它们的关系就是这样:
那么如何标识一个mtr中的一组 redo log 呢?此时要分两种情况:
- 需要保证原子性的操作只生成一条redo日志
- 通过在redo日志的 type 字段第一个比特位设置标识,如果type字段的第一个比特位为1,代表该需要保证原子性的操作只产生了单一的一条redo日志,否则表示该需要保证原子性的操作产生了一系列的redo日志。


- 需要保证原子性的操作会生成多条redo日志
- 在该组中的最后一条redo日志后边加上一条特殊类型的redo日志,该类型名称为
MLOG_MULTI_REC_END,type字段对应的十进制数字为31,该类型的redo日志结构很简单,只有一个type字段:
- 在该组中的最后一条redo日志后边加上一条特殊类型的redo日志,该类型名称为
2.1.4.2 Redo Log 写入 Redo Log Buffer
- Redo Log block(页)
为了更好的进行系统崩溃恢复,InnoDB把通过mtr生成的redo日志都放在了大小为512字节的页中。为了和表空间中的页做区别,这里把用来存储redo日志的页称为block(页和block的意思其实差不多)。一个redo log block的示意图如下:
真正的redo日志都是存储到占用496字节大小的log block body中,图中的log block header和log block trailer存储的是一些管理信息
- redo日志缓冲区
为了解决磁盘速度过慢的问题,写入redo日志时不能直接写到磁盘上,实际上在服务器启动时就向操作系统申请了一大片称之为redo log buffer的连续内存空间,翻译成中文就是redo日志缓冲区,可以简称为log buffer。这片内存空间被划分成若干个连续的redo log block,就像这样:
我们可以通过启动参数innodb_log_buffer_size来指定log buffer的大小,在MySQL 5.7.21这个版本中,该启动参数的默认值为16MB。
2.1.4.3 Redo Log 刷盘规则
在 MySQL 的 InnoDB 存储引擎中,通过提交事务时强制执行写日志操作机制实现事务的持久化。 InnoDB 存储引擎为了保证在事务提交时,将日志提交到事务日志文件中,默认每次将 Redo Log Buffer 中的日志写入日志文件时,都调用一次操作系统的fsync()操作。因为 MySQL 进程和其占用的内存空间都工作在操作系统的用户空间中,所以 MySQL 的 Log Buffer 也工作在操作系统的用户空间。默认情况下,如果想要将 Log Buffer 中的数据持久化到磁盘的日志文件中,还需要经过操作系统的内核空间缓冲区,也就是 OS Buffer。从 Redo Log Buffer 中将数据持久化到磁盘的日志文件中的大致流程如下图所示:
从图中可以看出, Redo Log 从用户空间的 Log Buffer 写入磁盘的 Redo Log 文件时需要经过内核空间的 OS Buffer。 这是因为在打开日志文件时,没有使用 O_DIRECT 标志位,而 O_DIRECT 标志位可以不经过操作系统内核空间的 OS Buffer,直接向磁盘写数据。在 InnoDB 存储引擎中, Redo Log 具有以下几种刷盘规则。
- 开启事务,发出提交事务指令后是否刷新日志由变量 innodb_flush_log_at_trx_commit 决定;
- 每秒刷新一次,刷新日志的频率由变量 innodb_flush_log_at_timeout 的值决定,默认是1s。需要注意的是,刷新日志的频率和是否执行了 commit 操作无关;
- 当 Log Buffer 中已经使用的内存超过一半时,也会触发刷盘操作;
- 当事务中存在 checkpoint(检查点)时,在一定程度上代表了刷写到磁盘时日志所处的 LSN 的位置。其中,LSN( Log Sequence Number)表示日志的逻辑序列号。
接下来,对第1条规则进行简单介绍。
当事务提交时,需要先将事务日志写入 Log Buffer,这些写入 Log Buffer 的日志并不是随着事务的提交立刻写入磁盘的,而是根据一定的规则将 Log Buffer 中的数据刷写到磁盘,从而保证了 Redo Log 文件中数据的持久性。这种刷盘规则可以通过 innodb_flush_log_at_trx_commit 变量控制,它可取的值有0、1和2,默认为1。每个取值代表的刷盘规则如图2-3所示。
- 该变量设置为0,则每次提交事务时,不会将 Log Buffer 中的日志写入 OS Buffer,而是通过一个单独的线程,每秒写入 OS Buffer 并调用
fsync()函数写入磁盘的 Redo Log 文件。这种方式不是实时写磁盘的,而是每隔1s写一次日志,如果系统崩溃,可能会丢失1s的数据; - 如果该变量设置为1,则每次提交事务都会将 Log Buffer 中的日志写入 OS Buffer,并且会调用
fsync()函数将日志数据写入磁盘的Redo Log文件中。这种方式虽然在系统崩溃时不会丢失数据,但是性能比较差。如果没有设置 innodb_flush_log_at_trx_commit 变量的值,则默认为1; - 如果该变量设置为2,则每次提交事务时,都只是将数据写入 OS Buffer,之后每隔1s,通过
fsync()函数将 OS Buffer 中的日志数据同步写入磁盘的 Redo Log 文件中;
需要注意的是,在MySQL中,有一个变量 innodb_flush_log_at_timeout 值为1,这个变量表示刷新日志的频率。另外,在 InnoDB 存储引擎中,刷新数据页到磁盘和刷新 Undo Log 页到磁盘就只有一种检查点规则。
2.2 Undo Log
2.2.1 Undo Log 基本概念
Undo Log 在 MySQL 事务中主要起到两方面作用:回滚事务和MVCC机制,是保证事务原子性的主要方式
2.2.2 Undo Log 基本原理
Undo Log 写入磁盘时和Redo Log 一样,默认情况下都需要经过内核空间的OS Buffer。同样,如果在打开日志文件时设置了 O_DIRECT 标志位,就可以不经过操作系统内核空间的OS Buffer,直接向磁盘写入数据。
在 MySQL 启动事务之前,会先对要修改的数据记录存储到 Undo Log 中(先到 Undo Log Buffer 中,然后数据会持久化到磁盘的 Undo Log 文件中),MySQL 的 Undo Log 是要写到一种专门存储 Undo Log 的页面中的。如果一个事务写入的 Undo Log 非常多,需要占用多个 undo 页面,那这些页面会被串联成一个链表,称作 undo页面链表。因为 MySQL 事务执行过程中产生的 undo log 也需要进行持久化操作,所以 undo log 也会产生 redo log。
在 MySQL 提交事务时,并不会立即删除相应的 Undo Log。此时 InnoDB 存储引擎会将当前事务对应的 Undo Log 放入待删除的列表,通过一个后台线程 purge thread 进行删除处理;
当数据库发生故障重启或者事务回滚时, InnoDB 存储引擎会读取 Undo Log 中的数据,将事务还未提交的数据回滚到最初的状态。
2.2.3 Undo Log 存储方式
Undo Log 默认存储到系统(共享)表空间,默认为 ibdata1 文件中。innodb_undo_tablespaces默认值为0,表示不独立设置 undo tablespace,默认记录到 ibdata 中;否则,则在 undo 目录下创建这么多个 undo 文件,例如假定设置该值为16,那么就会创建命名为undo001~undo016的undo tablespace文件,每个文件的默认大小为10M。如果开启了 innodb_file_per_table 参数,就会将 Undo Log 存放在每张数据表的 .ibd
上述在 MTR 之前,对于修改后的 undo 页面并没有加入 buffer pool 的 flush 链表,记录的 redo log 也没有加入到 redo log buffer,只有执行完 MTR 后,才会:
- 先将这个过程产生的 redo log 写入到 redo log buffer;
- 再将这个过程修改的 undo 页面加入到 buffer pool 的 flush 链表中;
因此,我们可以粗略的认为修改 undo 页面的 redo log 是先写的,而修改页面的过程是后发生的。
小贴士:
MySQL 把对底层页面的一次原子修改称作一个 Mini Trasaction,即 MTR。一个 MTR 中包含若干条redo 日志,在崩溃恢复时,要么全部恢复该 MTR 对应的redo日志,要么全部不恢复。
2.3 Binlog
binlog 是 MySQL server 层产生的逻辑日志,记录所有数据库表结构变更(例如CREATE、ALTER TABLE…)以及表数据修改(INSERT、UPDATE、DELETE…)的二进制日志。如果 UPDATE 操作没有造成数据变化,也是会记入binlog。
binlog 不会记录 SELECT 和 SHOW 这类操作,因为这类操作对数据本身并没有修改,但你可以通过查询通用日志来查看 MySQL 执行过的所有语句。
binlog常见格式
这块知识我用一个表格来表示,没必要啰嗦一大堆。
| format | 定义 | 优点 | 缺点 |
|---|---|---|---|
| statement | 记录的是修改SQL语句 | 日志文件小,节约IO,提高性能 | 准确性差,对一些系统函数不能准确复制或不能复制,如now()、uuid()等 |
| row | 记录的是每行实际数据的变更 | 准确性强,能准确复制数据的变更 | 日志文件大,较大的网络IO和磁盘IO |
| mixed | statement和row模式的混合 | 准确性强,文件大小适中 | 有可能发生主从不一致问题 |
业内目前推荐使用的是row模式,准确性高,虽然说文件大,但是现在有SSD和万兆光纤网络,这些磁盘IO和网络IO都是可以接受的。

