【MySQL】redo、undo、binlog日志的作用和实现原理

🌟 前言

🐶 作者简介:大家好,我是周周,目前就职于国内短视频小厂BUG攻城狮一枚。
💻个人主页:程序猿周周
📖专题系列:Java面试总结
🤺 如果文章对你有帮助,记得👍点赞👍、👀关注👀➕👌收藏👌,一键三连哦,你的支持将成为我最大的动力。

1 概述

日志是 MySQL 数据库的重要组成部分。MySQL 日志记录了 MySQL 数据库日常操作和错误信息。MySQL 有不同类型的日志文件(各自存储了不同类型的日志),从日志当中可以查询到 MySQL 的运行情况、用户的操作、错误的信息等。且当 MySQL 意外宕机时,可以通过这些日志文件进行数据恢复。

1.1 日志分类

MySQL 主要包含一下六种日志:

  • redo log(重做日志)
  • undo log(回滚日志)
  • bin log(二进制日志)
  • error log(错误日志)
  • slow query log(慢查询日志)
  • general log(一般查询日志)

对于主从复制结构中的 Slave 节点,还有一种 delay log(中继日志)。

1.2 日志作用

其中 redolog 和 undolog 与 MySQL 的事务操作息息相关,binlog 主要应用于主从复制,当然与事务操作也有一定的关系,理解这三种日志,对理解 MySQL 中的事务操作有着重要的意义。

1.2.1 错误日志

错误日志记录着 MySQL 启动和停止,以及服务器在运行过程中发生的错误及警告相关信息。当数据库意外宕机或发生其他错误时,我们应该去排查错误日志。

错误日志是默认开启的,可通过 show global variables like '%log_error%' 查找到相关配置。

mysql> show global variables like '%log_error%';
+----------------------------+----------------------------------------+
| Variable_name              | Value                                  |
+----------------------------+----------------------------------------+
| binlog_error_action        | ABORT_SERVER                           |
| log_error                  | ./localhost.err                        |
| log_error_services         | log_filter_internal; log_sink_internal |
| log_error_suppression_list |                                        |
| log_error_verbosity        | 2                                      |
+----------------------------+----------------------------------------+
5 rows in set (0.00 sec)
1.2.2 慢查询日志

慢查询日志是用来记录执行时间超过 long_query_time 这个变量定义时长的查询语句。通过慢查询日志,可以查找出哪些查询语句的执行效率很低,以便进行优化。并且 MySQL 还提供了专门用来分析满查询日志的工具程序 mysqldumpslow,用来帮助数据库管理人员解决可能存在的性能问题。

参数描述默认值
slow_query_log是否启用慢查询日志默认为0,可设置为0,1
slow_query_log_file指定慢查询日志位置及名称默认为 host_name-slow.log
long_query_time慢查询执行时间阈值,超过此时间会记录默认为10,单位为s
log_output慢查询日志输出目标默认为file,即输出到文件

慢查询日志默认是不开启的,但一般情况下建议开启,方便进行慢 SQL 优化。

1.2.3 一般查询日志

一般查询日志又称通用查询日志,是 MySQL 中记录最详细的日志,该日志会记录 MySQL 服务器所有操作,包括客户端何时连接和断开服务器,并记录从客户端收到的每个 SQL 语句。

默认情况下也是关闭的,开启通用查询日志会增加很多磁盘 I/O, 所以如非出于调试排错目的,不建议开启通用查询日志。

2 redo 日志

InnoDB 有一个最重要的概念就是缓冲池,这是一个在内存中分配的区域,InnoDB 会将数据首先缓存在此,请求首先去命中缓冲池,无法命中缓冲池的才会在磁盘上进行检索,被检索到的数据还是会缓存在缓冲池中。

但此时也引入了一个问题,如果由于某种原因导致 MySQL 宕机还未来得及刷页的话就会导致数据丢失,因此引入了重做日志。因此 redolog 不同于其它日志,是作用在 InnoDB 存储引擎层次上的。

redolog 记录的是数据库中每个页的修改,而不是某一行或某几行修改成怎么样,可以用来恢复提交后的数据页,并且只能恢复至最后一次提交的位置。

2.1 整体流程

redo 日志并非直接写入磁盘,而也有对于的日志缓冲区。MySQL 服务器在启动时就会申请一块叫做 redo log buffer 的连续内存空间,并被分为若干 redo log block(一块 512k 的空间,用于存储一个不可分割的过程 MTR)。

redolog 对于 redo log buffer 采用循环写入的方式,即当 redolog 写满后从头开始写,形成一个环状。因为 redolog 记录的是数据页上的修改,如果缓存池的数据页已经刷盘了,那 redolog 中记录的信息就失去了价值,新日志就可将这些失效记录进行擦除覆盖。

缓存中未刷到磁盘的数据称为脏数据(dirty data)。由于数据和日志都以页的形式存在,所以脏页包含脏数据和脏日志。write pos 表示 redolog 当前记录的日志序列号(LSN),写入还未刷盘的记录,循环往后递增;check point 表示 redolog 中的修改记录已经刷新到磁盘后的 LSN,这个 LSN 之前的数据已经全部落盘。

write pos 和 check point 之间的绿色部分表示空余部分,用来记录写的日志,check point 到 write pos 之间是还未来得及刷盘的部分。当 write pos 追上 check point 时,就需要推动 check point 前移,即进行刷盘空出位置进行记录新的日志。

可以通过 show engine innodb status 命令查看 LSN 的情况:

mysql> show engine innodb status\G;
---
LOG
---
Log sequence number     65193384  # 当前 LSN
Log flushed up to       65193384  # 刷新到日志文件的 LSN
Pages flushed up to     65193384  # 写入磁盘 dirty page 上的LSN
Last checkpoint at      65193384  # 写入磁盘 checkpoint 上的LSN

当 redolog 写满进行擦除前,需要确认这些要被擦除的数据对应内存中的数据页都已经全部落盘。并且擦除记录这段期间不能接受更新请求,必然会导致 MySQL 性能下降。所以在高并发场景下,合理的调整 redolog 大小非常重要。

2.2 持久化

MySQL 通过 Force Log at Commit 机制实现事务的持久性。即当事务提交时,先将 redo log buffer 写入到 redo log file 进行持久化,待 commit 操作完成时事务才算完成。这种做法也被称为 Write-Ahead Log(预写式日志,简称 WAL),在持久化一个数据页之前,先将内存中相应的日志页持久化。

这样带来两个方面的好处:

  • 在日志提交的时候只有日志文件需要冲刷到磁盘;而不是事务修改的所有数据文件,因此显著地减少了磁盘写的次数。

  • 日志文件是顺序写的,因此同步日志的开销要远比同步数据页的开销要小。

2.3 刷盘策略

redo buffer 以何种策略持久化到 redolog 可以通过 innodb_flush_log_at_trx_commit 进行设置:

描述
0每次事务提交时不进行刷盘操作,由 master thread 完成
1表示每次事务提交时都将进行 fsync 刷盘操作(默认值)
2表示每次事务提交时都只把 redo log buffer 内容写入 page cache,即 write 操作

另外,InnoDB 存储引擎有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。

此外,当 MySQL 启动时,无论上次是正常关闭还是异常退出,都会进行恢复操作,先检查数据页中的 LSN,如果这个 LSN 小于 redolog 中的 LSN(write pos)位置,则说明 redolog 上存在未完成刷盘的记录,此时会从最近的 check point 出发开始同步数据。

2.4 两次写功能

当发生数据库宕机时,可能存在 InnoDB 正在写入某个页到表,而这个页只写了一部分之后就发生了宕机的情况,这种情况被称为部分写失效(partial page write)

部分写失效的问题,依靠重做日志无法恢复,因为重做日志是基于偏移量的物理操作,如果这个页本身已经发生了损坏,再对其进行重做是没有意义的。此时在使用重做日志前,用户如果有一个页的副本,当发生写入失效时,可以先通过页的副本来还原该页再进行重做,这就是 doublewrite

doublewrite 由 内存中的 doublewrite buffer,大小为2MB 和物理磁盘上共享表空间中连续的 128 个页,即 2 个区(extent),大小同样为 2MB 两部分组成。

当刷新缓冲池脏页时,并不直接写到数据文件中,而是按照下面的路程执行:

1)拷贝页数据至内存中的两次写缓冲区。

2)从两次写缓冲区分两次写入磁盘共享表空间中,每次 1MB。

3)待第2步完成后,再将两次写缓冲区写入数据文件。

这样就可以解决部分写失效的问题,因为在磁盘共享表空间中已有数据页副本拷贝,如果数据库在页写入数据文件的过程中宕机,在实例恢复时,可以从共享表空间中找到该页副本,将其拷贝覆盖原有的数据页,再应用重做日志即可。

其中第 2 步是额外的性能开销,但由于磁盘共享表空间是连续的,因此开销不是很大。可以通过 skip_innodb_doublewrite 参数关闭两次写功能,默认是开启的。

3 undo 日志

前面我们讲到 redolog 可以在服务器意外宕机时用来恢复数据,保证已提交事务的持久性。但可能出现在事务还未提交的时候,写入的 redolog 已经被刷盘的情况,那么这些未提交的事务在 MySQL 重启时也被跟着恢复了,这样就违背了事务的原子性。

为了解决这个问题,InnoDB 又引入了另一种事务日志,即回滚日志,用于记录数据修改前的状态。在数据修改的流程中,记录一条与当前操作相反的回滚日志。即 redolog 是物理日志,而 undolog 是逻辑日志。

  • 作用

1)实现事务原子性,利用 undolog 进行回滚;

2)实现 MVVC 机制:undolog 中保存了未提交之前版本数据,所以可以作为旧版本数据的快照以便其他事务进行读取。

  • 位置

与 redolog 存放在重做日志文件不同的是,undolog 存放在数据库内部的一个特殊的段(segment)中,称为 undo segment。undo 段位于共享表空间内。InnoDB 对 undo 的管理同样采用段的方式,称为 rollback segment,每个回滚段记录了 1024 个 undo 段。(5.6.3 之后可通过 innodb_undo_tablespace 设置 undo 存储的位置)

  • 内容

在 InnoDB 存储引擎中,undolog 又被分为 insert undo log 和 update undo log。

insert undo log 是指在 insert 操作中产生的 undolog,因为 insert 操作的记录只对事务本身可见,对其他事务不可见,故该 undolog 可以在事务提交后直接删除,不需要进行 purge 操作。

而 update undo log 记录的是对 delete 和 update 操作产生的 undo log,该日志可能需要提供 MVCC 机制,因此不能再事务提交时就进行删除。提交时放入 undolog 链表,等待 purge 线程进行最后的删除。(purge 线程两个主要作用是:清理 undo 页和清除 page 里面带有 Delete_Bit 标识的数据行)

需要注意的是,假如一个事务中一条记录被多次修改,但是 undolog 只会记录原始版本的一条数据,每当对数据进行修改时,都会写入 redolog。

4 binary 日志

前面我们介绍的介绍的 redo 日志和 undo 日志都是在 InnoDB 引擎上的日志,而 binlog 则是在 Server 层进行操作的日志。因为在 MySQL 5.5 之前默认前使用的 MyISAM 引擎,并不支持事务,所以 InnoDB 只能自己实现一套新的日志系统。

4.1 基础概述

  • 日志功能

binlog 以二进制形式存储在磁盘中的逻辑日志,记录了所有 DDL 和 DML 操作(不包括 select 和 show),默认情况下是关闭的。主要作用于主从同步以及基于时间点的数据还原。

  • 主从同步

1)主库执行 DDL 和 DML 操作,按照修改顺序以此写入 binlog。

2)从库的 IO 线程连接上主库并请求读取指定位置 position 的日志内容。

3)主库收到请求后,将指定位置 position 之后的内容日志、主库 binlog 文件名称以及在日志中的位置推送给从库。

4)从库 IO 线程收到数据后,将日志内容以此写入 relaylog 文件最末端,并将 binlog 文件名和位置 position 记录到 master-info 文件中,以便下次使用。

5)从库的 SQL 线程检测到 relaylog 中内容更新后,读取日志并解析成可执行的语句进行主从同步。

  • 文件模式
模式描述
ROW记录每一行数据被修改的情况 ,然后 Slave 对相同的数据进行修改。优点:能清除记录每一行数据修改细节,能完全实现主从数据同步和数据恢复;缺点:批量操作会产生大量日志,尤其是 alter table 会让日志暴涨
STATEMENT每一条被修改的数据的 SQL 会记录到 binlog 中,Slave 在复制的时候 SQL 进程会解析成和原来 Master 执行过的相同的 SQL 再次执行,简称 SQL 语句复制。优点:日志量小,减少磁盘 IO,提升存储和恢复速度;缺点:在某些情况下会导致主从数据不一致,例如 now() 函数
MIXED以上两种模式混合使用,对于 STATEMENT 模式无法复制的使用 ROW 模式保存 binlog,MySQL 会根据执行的 SQL 自行选择写入模式

4.2 redolog

一条 update xx set A = A+1 where id = 2 语句的执行过程大概如下:

1)先查到对应数据 A 的主键 id = 2,通过主键索引来找到这条数据所在的页号。然后在 Buffer Pool 中进行查询,如果查到,则直接操作,如果查不到,则从磁盘内进行读取。

2)执行器拿到引擎给的这条数据进行 +1 操作后,再调用引擎接口写入这条数据。

3)引擎将这条数据更新至内存,同时记录到 redolog,此时 redolog 处于 prepare 态,这就告诉执行器更新已经完成,随时可以提交事务。

4)执行器生成这个操作的 binlog ,并把 binlog 写入磁盘。

5)执行器调用引擎的事务接口,引擎把刚刚的 redolog 改为提交状态,这个事务执行完成。

  • redolog 为何两阶段提交

如果没有两阶段提交,无论先写 redolog 还是先写 binlog 都会导致 redolog 和 binlog 记录的操作不一致,那么数据库的状态就有可能和日志恢复出来的数据不一致。

此时 MySQL 数据恢复过程:

1)读取 redo 日志,并从 checkpoint 开始重放(重新在内存中执行对应数据页的修改,然后按照正常流程进行数据刷盘)。

2)检查 redolog 中处于 prepare 状态的完整事务。

3)通过这些事务的 xid 在 binlog 中查询事务是否完整,完整则在 redolog 中设置为 commit 状态,否则根据 xid 在 undolog 找到事务并进行回滚。

  • redolog 和 binlog 区别
区别redologbinlog
实现层次InnoDB 引擎特有MySQL 服务器层
日志格式物理日志,记录数据页的变化逻辑日志,记录的是语句的原始逻
写入方式循环写的,固定大小空间追加写入,并不会覆盖之前的记录
记录时机记录事务发起后的 DML 和 DDL 语句记录 commit 完成后的 DML 语句和 DDL 语句
作用不同作为异常宕机或者介质故障后的数据恢复使用作为基于时间点的恢复数据使用,主从复制搭建
  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿周周

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值