MySQL 为什么需要 redo log?

[TOC]

今天想和大家聊一聊 MySQL 中的 redo log,其实最早我是想聊两阶段提交的,后来想想可能有小伙伴还不了解 binlog,所以就先整了一篇 binlog:

binlog 大家懂了之后,接下来还差个 redo log,redo log 大家也懂了,那么再讲两阶段提交相信小伙伴们就很容易懂了,咱们一步一步来。

1. 谁的 redo log

学习 redo log,我觉得首先要搞明白一个问题,就是是谁的 redo log?

我们知道,MySQL 架构整体上分为两层:Server 层和存储引擎层,如下图:

前面松哥文章+视频跟大家聊的 binlog,是 MySQL 自己提供的 binlog,而 redo log 则不是 MySQL 提供的,而是存储引擎 InnoDB 自己提供的。所以在 MySQL 中就存在两类日志 binlog 和 redo log,存在两类日志既有历史原因(InnoDB 最早不是 MySQL 官方存储引擎)也有技术原因,这个咱们以后再细聊。

先把这个问题搞清楚,后面很多地方就容易懂了。

2. buffer pool

在正式介绍 redo log 之前,还有一个 buffer pool 需要大家了解。

小伙伴们知道,InnoDB 引擎存储数据的时候,是以页为单位的,每个数据页的大小默认是 16KB,我们可以通过如下命令来查看页的大小:

16384/1024=16

刚好是 16KB。

计算机在存储数据的时候,最小存储单元是扇区,一个扇区的大小是 512 字节,而文件系统(例如 XFS/EXT4)最小单元是块,一个块的大小是 4KB,也就是四个块组成一个 InnoDB 中的页。我们在 MySQL 中针对数据库的增删改查操作,都是操作数据页,说白了,就是操作磁盘。

但是大家想想,如果每一次操作都操作磁盘,那么就会产生海量的磁盘 IO 操作,如果是传统的机械硬盘,还会涉及到很多随机 IO 操作,效率低的令人发指。这严重影响了 MySQL 的性能。

为了解决这一问题,MySQL 引入了 buffer pool,也就是我们常说的缓冲池。

buffer pool 的主要作用就是缓存索引和表数据,以避免每一次操作都要进行磁盘 IO,通过 buffer pool 可以提高数据的访问速度。

通过如下命令可以查看 buffer pool 的默认大小:

134217728/1024/1024=128

默认大小是 128MB,因为松哥这里的 MySQL 是安装在 Docker 中,所以这个分配的小一些。一般来说,如果一个服务器只是运行了一个 MySQL 服务,我们可以设置 buffer pool 的大小为服务器内存大小的 75%~80%。

3. change buffer

在正式介绍 redo log 之前,还有一个 change buffer 需要大家了解。

前面我们说的 buffer pool 虽然提高了访问速度,但是增删改的效率并没有因此提升,当涉及到增删改的时候,还是需要磁盘 IO,那么效率一样低的令人发指。

为了解决这个问题,MySQL 中引入了 change buffer。change buffer 以前并不叫这个名字,以前叫 insert buffer,即只针对 insert 操作有效,现在改名叫 change buffer 了,不仅仅针对 insert 有效,对 delete 和 update 操作也是有效的,change buffer 主要是对非唯一的索引有效,如果字段是唯一性索引,那么更新的时候要去检查唯一性,依然无法避免磁盘 IO。

change buffer 就是说,当我们需要更改数据库中的数据的时候,我们把更改记录到内存中,等到将来数据被读取的时候,再将内存中的数据 merge 到 buffer pool 然后返回,此时 buffer pool 中的数据和磁盘中的数据就会有差异,有差异的数据我们称之为脏页,在满足条件的时候(redo log 写满了、内存写满了、其他空闲时候),InnoDB 会把脏页刷新回磁盘。这种方式可以有效降低写操作的磁盘 IO,提升数据库的性能。

通过如下命令我们可以查看 change buffer 的大小以及哪些操作会涉及到 change buffer:

  • innodb_change_buffer_max_size:这个配置表示 change buffer 的大小占整个缓冲池的比例,默认值是 25%,最大值是 50%
  • innodb_change_buffering:这个操作表示哪些写操作会用到 change buffer,默认的 all 表示所有写操作,我们也可以自己设置为 none/inserts/deletes/changes/purges 等。

不过 change buffer 和 buffer pool 都涉及到内存操作,数据不能持久化,那么,当存在脏页的时候,MySQL 如果突然挂了,就有可能造成数据丢失(因为内存中的数据还没写到磁盘上),但是我们在实际使用 MySQL 的时候,其实并不会有这个问题,那么问题是怎么解决的?那就得靠 redo log 了。

4. redo log 的诞生

在正式介绍 redo log 之前,还需要给大家普及一个概念:WAL。

WAL 全称是 Write-Ahead Logging 中文译作预写日志。啥意思呢?就是说 MySQL 的写操作并不是立刻更新到磁盘上,而是先记录在日志上,然后在合适的时间再更新到磁盘上,这样的好处是错开高峰期的磁盘 IO,提高 MySQL 的性能。

配合上前面的 buffer pool 和 change buffer,WAL 就是说在操作 buffer pool 和 change buffer 之前,会先把记录写到 redo log 日志中,然后再去更新 buffer pool 或者 change buffer,这样,即使系统突然崩了,将来也可以通过 redo log 恢复数据。当然,redo log 本身又分为:

  • 日志缓冲(redo log buffer),该部分日志是易失性的。
  • 重做日志(redo log file),这是磁盘上的日志文件,该部分日志是持久的。

那有人说,写 redo log 不就是磁盘 IO 吗?而写数据到磁盘也是磁盘 IO,既然都是磁盘 IO,那干嘛不把直接把数据写到磁盘呢?还费这事!

此言差矣。

写 redo log 跟写数据有一个很大的差异,那就是 redo log 是顺序 IO,而写数据涉及到随机 IO,写数据需要寻址,找到对应的位置,然后更新/添加/删除,而写 redo log 则是在一个固定的位置循环写入,是顺序 IO,所以速度要高于写数据。

如前文所说,redo log 涉及到两个东西:redo log buffer 和 redo log file,这两个东西我们分别来介绍。

4.1 redo log buffer

先来说 redo log buffer。

我们说数据的变化先写入 redo log 中,并不是上来就写磁盘,也是先写到内存中,即 redo log buffer,在时机成熟时,再写入磁盘,也就是 redo log file。

我们先来看看 redo log buffer 有多大:

16777216 ÷ 1024 ÷ 1024 = 16MB

可以看到,这个 redo log buffer 大小刚好是 16MB,如果你觉得这个值有点小,也可以自行修改其大小。

数据的变更都会首先记录在这块内存中。小伙伴们知道,MySQL 的增删改,如果我们没有显式的开启事务,MySQL 内部也是有一个事务存在的,当内部这个事务 commit 的时候,redo log buffer 会持久化到磁盘中。

具体来说,有如下几个持久化时机:

  1. innodb_flush_log_at_trx_commit

通过 innodb_flush_log_at_trx_commit 参数来控制持久化时机,该参数默认值为 1,如下图:

当然开发者可根据自己的实际需求修改该参数。该参数有三种取值,含义分别如下:

  • 0:每秒一次,将 redo log buffer 中的数据刷新到磁盘中。
  • 1:每次 commit 时,将 redo log buffer 中的数据刷新到磁盘中,即只要 commit 成功,磁盘上就有对应的 redo log 日志,这是最安全的情况,也是推荐使用的参数
  • 2:每次 commit 时,将 redo log buffer 中的数据刷新到操作系统缓存中,操作系统缓存中的数据每秒刷新一次,会持久化到磁盘中。

这是第一种 redo log buffer 持久化的时机。

  1. 当 redo log buffer 的使用量达到 innodb_log_buffer_size 的一半时,将其写入磁盘成为 redo log file。
  2. MySQL 关闭时,将 redo log buffer 写入磁盘成为 redo log file。

那如果 redo log buffer 中的数据还没有磁盘,MySQL 就挂了该怎么办?没写入磁盘,说明你还没 commit,既然没 commit,那就数据修改操作都还没有完成,那只能丢了就丢了,如果已经 commit 了,那么数据就会持久化到 redo log file 中,此时即使 MySQL 挂了,将来 MySQL 重启恢复了,数据也是可以被恢复的。具体的恢复逻辑,就涉及到两阶段提交了,这个松哥在后面的文章中再和大家详细介绍。

4.2 redo log 落盘

还有一个需要大家注意的问题就是 redo log 落盘,落盘的数据从哪里来?是从 redo log 日志中来还是从 buffer pool 中来?

在前面的文章中我们说过:binlog 是一种逻辑日志,他里边所记录的是一条 SQL 语句的原始逻辑,例如给某一个字段 +1,这区别于 redo log 的物理日志,物理日志记录的是在某个数据页上做了什么修改。

由于 redo log 并没有记录数据页的完整数据,所以正常的落盘其实用不到 redo log,数据落盘的时机到了时,直接拿着将脏页(buffer pool)持久化到磁盘中即可。

好啦,今天就和大家分享这么多,redo log 还有一些内容,我们在后面的文章中再继续聊~

参考资料:

# MySQL

喜欢这篇文章吗?扫码关注公众号【江南一点雨】【江南一点雨】专注于 SPRING BOOT+微服务以及前后端分离技术,每天推送原创技术干货,关注后回复 JAVA,领取松哥为你精心准备的 JAVA 干货!

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×