写点什么

一文了解 MySQL 中的日志 redo log、undo log、binlog

作者:Ayue、
  • 2022 年 1 月 22 日
  • 本文字数:5086 字

    阅读完需:约 17 分钟

一文了解MySQL中的日志redo log、undo log、binlog

1. 什么是 WAL

什么是 WAL?


WAL(Write Ahead Log)预写日志,是数据库系统中常见的一种手段,用于保证数据操作的原子性和持久性


在计算机科学中,「预写式日志」(Write-ahead logging,缩写 WAL)是关系数据库系统中用于提供原子性和持久性(ACID 属性中的两个)的一系列技术。在使用 WAL 的系统中,所有的修改在提交之前都要先写入 log 文件中。log 文件中通常包括 redoundo 信息。


为什么需要使用 WAL,然后包含 redo 和 undo 信息呢?


举个例子,如果一个系统直接将变更应用到系统状态中,那么在机器掉电重启之后系统需要知道操作是成功了,还是只有部分成功或者是失败了(为了恢复状态)。如果使用了 WAL,那么在重启之后系统可以通过比较日志和系统状态来决定是继续完成操作还是撤销操作。


redo log 称为重做日志,每当有操作时,在数据变更之前将操作写入 redo log,这样当发生掉电之类的情况时系统可以在重启后继续操作。


undo log 称为撤销日志,当一些变更执行到一半无法完成时,可以根据撤销日志恢复到变更之间的状态。


重做日志和回滚日志与事务操作息息相关,MySQL 中用 redo log 来在系统崩溃重启之类的情况时修复数据(事务的持久性),而 undo log 来保证事务的原子性。

2. 重做日志 (redo log)

MySQL 中用 redo log 来在系统崩溃重启之类的情况时修复数据,保证事务的持久性。


InnoDB 存储引擎是以页为单位来管理存储空间的,我们进行的增删改查操作其实本质上都是在访问页面(包括读页面、写页面、创建新页面等操作)。


在真正访问页面之前,需要把在磁盘上的页缓存到内存中的 Buffer Pool 之后才可以访问。但是在事务的时候又强调过一个称之为持久性的特性,就是说对于一个已经提交的事务,在事务提交后即使系统发生了崩溃, 这个事务对数据库中所做的更改也不能丢失。


如果我们只在内存的 Buffer Pool 中修改了页面,假设在事务提交后突然发生了某个故障,导致内存中的数据都失效了,那么这个已经提交了的事务对数据库中所做的更改也就跟着丢失了,这是我们所不能忍受的。


那么如何保证这个持久性呢?


一个很简单的做法就是在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘,但是这个简单粗暴的做法有些问题:


刷新一个完整的数据页太浪费了


有时候我们仅仅修改了某个页面中的一个字节,但是我们知道在 InnoDB 中 是以页为单位来进行磁盘 IO 的,也就是说我们在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘,我们又知道一个页面默认是 16KB 大小,只修改一个字节就要刷新 16KB 的数据到磁盘上显然是太浪费了。


随机 IO 刷起来比较慢


一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,该事务修改的这些页面可能并不相邻,这就意味着在将某个事务修改的 Buffer Pool 中的 页面刷新到磁盘时,需要进行很多的随机 IO,随机 IO 比顺序 IO 要慢,尤其对于传统的机械硬盘来说。


**怎么办呢?**我们只是想让已经提交了的事务对数据库中数据所做的修改永久生效,即使后来系统崩溃,在重启后也能把这种修改恢复出来。


所以我们其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘,只需要把修改了哪些东西记录一下就好,比方说某个事务将系统表空间中的第 100 号页面中偏移量为 1000 处的那个字节的值 1 改成 2 我们只需要记录一下:将第 0 号表空间的 100 号页面的偏移量为 1000 处的值更新为 2。


这样我们在事务提交时,把上述内容刷新到磁盘中,即使之后系统崩溃了,重启之后只要按照上述内容所记录的步骤重新更新一下数据页,那么该事务对数据库中所做的修改又可以被恢复出来,也就意味着满足持久性的要求。因为在系统崩溃重启时需要按照上述内容所记录的步骤重新更新数据页,所以上述内容也被称之为重做日志,英文名为 redo log,也可以称之为 redo log。


与在事务提交时将所有修改过的内存中的页面刷新到磁盘中相比,只将该事务执行过程中产生的 redo log 刷新到磁盘的好处如下:


  1. redo log 占用的空间非常小

  2. 存储表空间 ID、页号、偏移量以及需要更新的值所需的存储空间是很小的。

  3. redo log 是顺序写入磁盘的

  4. 在执行事务的过程中,每执行一条语句,就可能产生若干条 redo 日志,这些日志是按照产生的顺序写入磁盘的,也就是使用顺序 IO。


简单来说,redo 日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统崩溃重启后可以把事务所做的任何修改都恢复出来。


总结一下:


1️⃣ redo 日志的作用


确保事务的持久性。redo 日志记录事务执行后的状态,用来恢复未写入 data file 的已成功事务更新的数据。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启 MySQL 服务的时候,根据 redo log 进行重做,从而达到事务的持久性这一特性。


2️⃣ 什么时候产生的


事务开始之后就产生 redo log,redo log 的落盘并不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入 redo log 文件中。


3️⃣ 什么时候释放


当对应事务的脏页写入到磁盘之后,redo log 的使命也就完成了,重做日志占用的空间就可以重用(被覆盖)。


4️⃣ 对应的物理文件


MySQL 的数据目录(使用 SHOW VARIABLES LIKE 'datadir'查看)下默认有两个名为 ib_logfile0ib_logfile1 的文件,log buffer 中的日志默认情况下就是刷新到这两个磁盘文件中。


innodb_log_group_home_dir:指定日志文件组所在的路径,默认./ ,表示在数据库的数据目录下。


innodb_log_files_in_group:指定重做日志文件组中文件的数量,默认 2。


innodb_log_file_size:该参数指定了每个 redo 日志文件的大小,默认值为 48MB。


innodb_mirrored_log_groups: 指定了日志镜像文件组的数量,默认 1。


5️⃣ redo log 什么时候写盘


在事务开始之后逐步写盘的。


之所以说重做日志是在事务开始之后逐步写入重做日志文件,而不一定是事务提交才写入重做日志缓存,原因就是,重做日志有一个缓存区 Innodb_log_buffer,Innodb_log_buffer 的默认大小为 8M,Innodb 存储引擎先将重做日志写入 innodb_log_buffer 中。然后会通过以下三种方式将 innodb 日志缓冲区的日志刷新到磁盘:


  1. 后台有一个线程,大约每秒都会刷新一次 log buffer 中的 redo 日志到磁盘。

  2. 每个事务提交时会将重做日志刷新到重做日志文件。

  3. 当重做日志缓存可用空间 少于一半时,重做日志缓存被刷新到重做日志文件。


因此重做日志的写盘,并不一定是随着事务的提交才写入重做日志文件的,而是随着事务的开始,逐步开始的。

3. 撤销日志 (undo log)

undo 日志用来保证事务的原子性。


我们说过事务需要保证原子性,也就是事务中的操作要么全部完成,要么什么也不做。但是偏偏有时候事务执行到一半会出现一些情况,比如:


  1. 事务执行过程中可能遇到各种错误,比如服务器本身的错误,操作系统错误,甚至是突然断电导致的错误。

  2. 程序员可以在事务执行过程中手动输入 ROLLBACK 语句结束当前的事务的执行。


这两种情况都会导致事务执行到一半就结束,但是事务执行过程中可能已经修改了很多东西,为了保证事务的原子性,我们需要把东西改回原先的样子,这个过程就称之为回滚(英文名:rollback),这样就可以造成这个事务看起来什 么都没做,所以符合原子性要求。


每当我们要对一条记录做改动时(这里的改动可以指 INSERT、DELETE、UPDATE),都需要把回滚时所需的东西都给记下来。比方说:


  • 插入一条记录时,至少要把这条记录的主键值记下来,之后回滚的时候只需要把这个主键值对应的记录删掉。 你删除了一条记录,至少要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中。

  • 修改了一条记录,至少要把修改这条记录前的旧值都记录下来,这样之后回滚时再把这条记录更新为旧值。


这些为了回滚而记录的这些东西称之为撤销日志,英文名为 undo log 。


这里需要注意的一点是,由于查询操作(SELECT)并不会修改任何用户记录,所以在查询操作执行时,并不需要记录相应的 undo 日志。当然,在真实的 InnoDB 中,undo 日志其实并不像我们上边所说的那么简单,不同类型的操作产生的 undo 日志的格式也是不同的。


1️⃣ undo 日志的作用


保证数据的原子性,保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。


2️⃣ 什么时候产生的


事务开始之前,将当前是的版本生成 undo log,undo 也会产生 redo 来保证 undo log 的可靠性。


3️⃣ 什么时候释放


当事务提交之后,undo log 并不能立马被删除,而是放入待清理的链表,由 purge 线程判断是否由其他事务在使用 undo 段中表的上一个事务之前的版本信息,决定是否可以清理 undo log 的日志空间。


4️⃣ 对应的物理文件


MySQL5.6 之前,undo 表空间位于共享表空间的回滚段中,共享表空间的默认的名称是 ibdata,位于数据文件目录中。


MySQL5.6 之后,undo 表空间可以配置成独立的文件,但是提前需要在配置文件中配置,完成数据库初始化后生效且不可改变 undo log 文件的个数。


如果初始化数据库之前没有进行相关配置,那么就无法配置成独立的表空间了。

4. 二进制日志 (binlog)

1️⃣ 作用


用于复制,在主从复制中,从库利用主库上的 binlog 进行重播,实现主从同步。


用于数据库的基于时间点的还原。


2️⃣ 什么时候产生


事务提交的时候,一次性将事务中的 sql 语句(一个事物可能对应多个 sql 语句)按照一定的格式记录到 binlog 中。


这里与 redo log 很明显的差异就是 redo log 并不一定是在事务提交的时候刷新到磁盘,redo log 是在事务开始之后就开始逐步写入磁盘。


因此对于事务的提交,即便是较大的事务,提交(commit)都是很快的,但是在开启了 bin_log 的情况下,对于较大事务的提交,可能会变得比较慢一些。


这是因为 binlog 是在事务提交的时候一次性写入的造成的。


3️⃣ 什么时候释放


binlog 的默认是保持时间由参数 expire_logs_days 配置,也就是说对于非活动的日志文件,在生成时间超过 expire_logs_days 配置的天数之后,会被自动删除。


mysql> SHOW VARIABLES LIKE 'expire_logs_days';+------------------+-------+| Variable_name    | Value |+------------------+-------+| expire_logs_days | 1     |+------------------+-------+1 row in set (0.01 sec)
复制代码


4️⃣ 对应的物理文件


配置文件的路径为 log_bin_basename,binlog 日志文件按照指定大小,当日志文件达到指定的最大的大小之后,进行滚动更新,生成新的日志文件。


对于每个 binlog 日志文件,通过一个统一的 index 文件来组织。


mysql> SHOW VARIABLES LIKE 'log_bin_basename';+------------------+-------+| Variable_name    | Value |+------------------+-------+| log_bin_basename |       |+------------------+-------+1 row in set (0.00 sec)
复制代码


二进制日志的作用之一是还原数据库的,这与 redo log 很类似,很多人混淆过,但是两者有本质的不同


  1. 作用不同

  2. redo log 是保证事务的持久性的,是事务层面的,binlog 作为还原的功能,是数据库层面的(当然也可以精确到事务层面的),虽然都有还原的意思,但是其保护数据的层次是不一样的。

  3. 内容不同

  4. redo log 是物理日志,是数据页面的修改之后的物理记录,binlog 是逻辑日志,可以简单认为记录的就是 sql 语句。


另外,两者日志产生的时间,可以释放的时间,在可释放的情况下清理机制,都是完全不同的。


关于事务提交时,redo log 和 binlog 的写入顺序,为了保证主从复制时候的主从一致(当然也包括使用 binlog 进行基于时间点还原的情况),是要严格一致的,MySQL 通过两阶段提交过程来完成事务的一致性的,也即 redo log 和 binlog 的一致性的,理论上是先写 redo log,再写 binlog,两个日志都提交成功(刷入磁盘),事务才算真正的完成。

5. 错误日志 (errorlog)

错误日志记录着 mysqld 启动和停止,以及服务器在运行过程中发生的错误的相关信息。在默认情况下,系统记录错误日志的功能是关闭的,错误信息被输出到标准错误输出。


显示错误日志:


mysql> SHOW VARIABLES LIKE '%err%';+---------------------+---------------------+| Variable_name       | Value               |+---------------------+---------------------+| binlog_error_action | ABORT_SERVER        || error_count         | 0                   || log_error           | /var/log/mysqld.log || log_error_verbosity | 3                   || max_connect_errors  | 100                 || max_error_count     | 64                  || slave_skip_errors   | OFF                 |+---------------------+---------------------+7 rows in set (0.00 sec)
复制代码

6. 慢查询日志 (slow query log)

关于查询可以看之前的文章:SQL慢查询优化

发布于: 刚刚阅读数: 2
用户头像

Ayue、

关注

🏆 InfoQ写作平台-签约作者 🏆 2019.10.16 加入

个人站点:javatv.net | 学习知识,目光坚毅

评论

发布
暂无评论
一文了解MySQL中的日志redo log、undo log、binlog