详解MySQL事务日志——redo log
redo log介绍
redo log又叫“重做日志”,是存储引擎层 (innoDB) 生成的日志,记录的是”物理级别”上的页修改操作,比如页号x,偏移量y写入了’z’数据,主要目的为了保证数据不丢失,当MySQL发生宕机的时候,可以利用redo log日志进行数据恢复,如下图所示。
默认的redo log日志文件为ib_logfile0, ib_logfile1,如下图:
那想过为什么要”多此一举”先写入到redo log磁盘文件中,然后再落到数据库表中?而不直接落到数据库表中?
主要是因为顺序IO性能远高于随机IO。
数据在MySQL中存储是以页为单位,事务中的数据可能遍布在不同的页中,如果直接写入到对应的页中,是随机IO写入。
而redo log是通过顺序IO”追加”的方式写入到文件末尾,而且写入的内容也是物理日志,比如,某个事务将系统表空间中第10号页面中偏移量为 100 处的那个字节的值 1 改成 2等信息,日志占用空间也很小。
redo log整体流程
事务在写入到数据库中涉及到redo log的整体流程如下图所示:
性能不够,缓存来凑。由于CPU的性能远远大于磁盘,为了消除这个鸿沟,引入了两个缓存,Buffer Pool和redo log buffer。Buffer Pool用来存放各种操作,比如写入数据时,先写到内存中,然后由后台线程再刷写到磁盘。redo log buffer用来存放重做日志,后续刷到磁盘中。
- 先将原始数据从磁盘中读入到Buffer Pool中
- 修改Buffer Pool中的数据
- 生成一条重做日志并写入redo log buffer,记录数据修改后的值
- 当事务提交时,将redo log buffer中的内容追加磁盘中的redo log文件中
- 将磁盘日志文件redo log file 内容刷到数据库表中
上面流程中这种先写日志,再写磁盘,只有日志写入成功,才算事务提交成功的技术思想在MySQL也叫做WAL技术 (Write-Ahead Logging)。
redo log落盘策略
事务的日志是先写入到redo log buffer 中是很快的,那如何保证redo log buffer中的信息高效的落到磁盘日志文件中呢?
- redo log buffer不是直接将日志内容刷盘到redo log file中。
- redo log buffer内容先刷入到操作系统的文件系统缓存 (page cache)中去,这个过程很快,而且整个系统宕机概率相对MySQL会小很多。
- 最后,日志内容会从操作系统的文件系统缓存中刷到磁盘的日志文件中,至于什么时候触发这个动作,MySQL的innoDB引擎提供了3种策略可选。
InnoDB引擎提供了 innodb_flush_log_at_trx_commit 参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 的3种策略。
innodb_flush_log_at_trx_commit=1
- 每次事务提交时都将进行同步, 执行主动刷盘操作,如上图的红线位置,所以只要事务提交成功,redo log记录就一定在硬盘里,不会有可数据丢失。
- 该种方式是MySQL innoDB存储引擎默认的刷盘机制。
- 如果事务执行期间MySQL挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。可以保证ACID的D,数据绝对不会丢失,但是效率最差的。
innodb_flush_log_at_trx_commit=2
- 为2时,只要事务提交成功,redo log buffer中的内容只写入文件系统缓存(pagecache)
- 如果仅仅只是MySQL挂了不会有任何数据丢失,但是操作系统宕机可能会有1秒数据的丢失,这种情况下无法满足ACID中的D
- 数值2的效率是高于数值等于1的
innodb_flush_log_at_trx_commit=0
- 为0时,后台线程每隔1秒进行一次重做日志的刷盘操作,因此MySQL挂了最多丢失1秒钟内的事务。
- 这种方式效率是最高的,这种策略也有丢失数据的风险,也无法保证持久性。
总结
redo-log刷盘会首先写入redo-log buffer中,然后后台有一个系统线程,1s执行一次,将redo-log buffer中的内容写入到page cache中,然后再刷盘到物理日志redo.file中。其中,刷盘时机有三个策略,默认为1:事务提交的时候就直接触发刷盘,直接将redo log buffer中的内容写入到page cache 中,然后刷盘到物理日志redo.file中。策略为2的时候,事务提交的时候,会将redo log buffer 中的内容写到 page cache中,但不会刷盘,会等待后台系统线程刷盘,坏处是mysql宕机了没问题,系统宕机paga cache会消失,存在page cache中没刷盘的数据会丢失。策略为3的时候,事务提交的时候什么都不会做,完全靠后台线程将redo log buffer中的内容刷新到 page cache中,然后再刷盘到redo.file中,mysql宕机的时候,redo log buffer中的数据会消失。
其他被动触发刷盘的场景
除了上面3种策略进行刷盘以外,还有两种场景会让一个没有提交的事务的 redo log 写入到磁盘中。
- redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动写盘。注意,由于这个事务并没有提交,所以这个写盘动作只是 write,而没有调用 fsync,也就是只留在了文件系统的 page cache。
- 并行的事务提交的时候,顺带将这个事务的 redo log buffer 持久化到磁盘。假设一个事务 A 执行到一半,已经写了一些 redo log 到 buffer 中,这时候有另外一个线程的事务 B 提交,如果 innodb_flush_log_at_trx_commit 设置的是 1,那么按照这个参数的逻辑,事务 B 要把 redo log buffer 里的日志全部持久化到磁盘。这时候,就会带上事务 A 在 redo log buffer 里的日志一起持久化到磁盘。
小结
我们可以根据实际的业务场景,在性能和持久性做一些权衡,但建议使用默认值,虽然操作系统宕机的概率理论小于数据库宕机的概率,但是一般既然使用了事务,那么数据的安全相对来说更重要些。
redo log写入数据页机制
目前事务日志已经落入到磁盘的redo log file中了,MySQL会去读取这个文件将数据写入到数据页中。
很显然,目前对redo log file会进行读和写的操作。在日志文件组中有两个重要的“指针”,分别是 write pos、checkpoint。
- write pos是当前记录的位置,一边写一边后移
- checkpoint是当前要擦除的位置,也是往后推移
- 每次刷盘 redo log 记录到日志文件组中,write pos 位置就会后移更新。
- 每次MySQL加载日志文件组恢复数据时,会清空加载过的 redo log 记录,并把checkpoint后移更新。
- 如果write pos 追上 checkpoint ,表示日志文件组满了,这时候不能再写入新的 redo log记录,MySQL 得停下来,清空一些记录,把 checkpoint 推进一下,如下图:
这就是整个redo log file中的日志恢复到数据页中的过程。