一、引子
上篇:《MySQL 是如何更新的?》中,我们介绍了
MySQL的两个重要的日志:binlog和redo log。
其中,binlog 对应 MySQL 在 Server 层的逻辑日志。redo log 对应 MySQL 在 InndoDB 引擎下的 Engine 层物理日志。
为了保证数据的一致性, MySQL 用一个事务将两个日志的写逻辑的一致性。(即“两阶段提交”)

两阶段提交
MySQL 的两阶段提交 + WAL技术(Write-Ahead Logging,先写日志再写盘),这两个结合在一起保证了数据不会丢失。
即:只要 redo log 和 binlog 持久化到磁盘,即使 MySQL 异常,重启后数据依然可以恢复。
那么,redo log 和 binlog 的写入流程具体是怎样的?具体是怎么保证的?
本篇,让我们一起详细了解下 redo log 和 binlog 的写入流程。
二、binlog 写入流程
首先,我们先了解下MySQL Server 层日志 —— binlog 的写入具体流程。
其实,binlog 的写入流程比较简单:
先把
binlog日志写到binlog cache。事务提交时,将
binlog cache写入binlog文件。
系统给每个线程的 binlog cache 分配了一片内存,大小为 binlog_cache_size。
如果超过这个大小,就会把cache暂存到磁盘的临时文件(tmp files)中。
这个很好理解,
binlog cache由内存和临时文件组件。
因为内存是有限的,在事务完成前不能直接写入最终日志文件,因此只能先临时写到临时文件(tmp files)中。


binlog写入流程
我们可以看到,每个线程都有自己的
binlog cache。但是,都共用同一份binlog文件。
write:
把日志写入文件系统内核的 page cache,page cache是 OS 对磁盘IO的缓存。
适合小文件传输。因为大文件传输 page cache 命中率低,这时不仅没有起到缓存作用,反而增加了一次数据从磁盘 buffer 到 内核 page cache 的开销。
fsync:
将数据持久化到磁盘。
fsync 才占磁盘的 IOPS(Input/Output Operations Per Second)。
MySQL 有一个参数:
sync_binlog,用来控制write和fsync的时机。
可以根据业务场景的需要,来具体调整。
| sync_binlog | 含义 |
|---|---|
| 等于 0 | 每次事务提交只write,不fsync。(不推荐) |
| 等于 1 | 每次事务提交不仅write,都会执行 fsync。(这个配置是最安全的,不会丢binlog日志) |
| 等于 N | 每次提交事务都write,累积N个事务后,一起fsync。(性能好,但是异常重启会丢N个事务的binlog日志) |
三、redo log 写入流程
了解了 binlog 的写入流程,下面我们来看看 redo log 的写入流程。
首先,写入
redo log buffer。其次,写入(
write)文件系统的page cache。最后,持久化(
fsync)到磁盘disk
是不是感觉和
binlog有点类似?
但实际上他们之间还是有很大差异的,下面我们了解下他们之间的差异。
InnoDB 提供了 innodb_flush_log_at_trx_commit 参数来控制 redo log 写入流程。
| innodb_flush_log_at_trx_commit | 含义* |
|---|---|
| = 0 | 每次事务提交时,redo log 只会留在 redo log buffer。(风险大,等待每秒 write + fsync 到 disk) |
| = 1 | 每次事务提交时,都将所有 redo log fsync到磁盘。(最安全) |
| = 2 | 每次事务提交时,都将 redo log write 到 page cache。 |
每秒刷盘机制
InnoDB 有一个后台线程,每隔1秒,就会把 redo log buffer中的日志,调用 write 写到 page cache,然后 fsync 持久化到磁盘。
需要注意的是,事务执行中的 redo log 也是存在于 redo log buffer 的,也会被一起持久化到磁盘。(也就是说,一个还没有提交事务的 redo log,也可能已经被持久化到磁盘了)
强制刷盘
其实,不光每秒刷盘会提前持久化 redo log 到磁盘。
当
redo log buffer到达innodb_log_buffer_size(缓冲池大小,默认是8MB)一半的时候,会主动触发write到文件系统的page cache。并行事务提交,顺带将其他事务的
redo log buffer持久化到磁盘。
举例:
事务A 执行到一半,写入了部分redo log到buffer中。
事务B 完成,进行提交。
如果innodb_flush_log_at_trx_commit设置为1,代表每次提交都会全部fsync到磁盘。这时候,事务A的redo log也有部分已经持久化了。
这时候,有同学可能会问:
两阶段提交,不是最后一步才会fsync到磁盘上么?为什么提前持久化了呢?会有啥影响吗?
首先,prepare 阶段提前fsync到磁盘并没有问题。
因为 binlog 还没有 ready。还没到最后的 commit,并没有实际执行。
其次,磁盘IOPS是有瓶颈的。MySQL这样设计可以降低磁盘IO,提高性能。
四、双“1”配置,最安全
只有在 sync_binlog 和 innodb_flush_log_at_trx_commit 都等于1的情况下,才能保证数据不丢失。
即
写 redo log 时,每次事务提交时,都将所有
redo logfsync到磁盘写 binlog 时,每次事务提交时,
binlog都会执行fsync到磁盘。
虽然,双1配置可以保证数据安全性。但是往往越安全,性能就会不如意。
在某些性能要求比较高的场景下,往往会故意打破双1配置。
(虽然在 MySQL异常时,会丢一些数据。但在大量数据的基数下,风险依然可控。)
问:为什么双“1”配置就一定是最安全的?能证明么?
答:别着急,等看完“五、真正的两阶段提交”,咱就明白了。
五、真正的两阶段提交



两阶段提交
之前,给大家介绍两阶段提交时,我画了这张图,比较好理解。
但其实由于MySQL redo log 组提交优化,真正的两阶段提交如下图:


真正的两阶段提交
MySQL这样设计的好处是,可以组提交,交叉fsync。
一组 fsync收集的write越多, 对应的磁盘的IO越少,提高MySQL性能。
因此,WAL的优势在于:
redo log&binlog都是按顺序写入磁盘的,比随机写磁盘速度快。组提交机制,合并
fsync。大幅度降低磁盘的IOPS消耗,提高IO性能。
证明数据安全
上图分为五个阶段,这样,我们在双“1”配置下,利用反证法来验证数据安全性。
如果
MySQL挂在了 1,2,3 阶段。
这时候,不论redo log 还是 binlog 都还没有 fsync 到磁盘。
因此,掉电导致内存丢失,实际也没写入磁盘,数据一致性。
如果
MySQL挂在了第 4 阶段fsync binlog。
即redo log fsync成功,binlog fsync失败。MySQL重启后,发现有redo log的磁盘数据,没有binlog磁盘数据。
发现是redo log处于prepare阶段,回滚(删除磁盘里的redo log)。如果
MySQL挂在了第 5 阶段commit。
即redo log和binlog都fsync成功,但commit失败。MySQL重启后,发现有完整的binlog和redo log,继续commit。
数据成功写入MySQL。
版权声明:本文内容来自简书>作者 : 齐舞647,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。原文链接:https://www.jianshu.com/p/c5754a4edcfa如有涉及到侵权,将立即予以删除处理。在此特别鸣谢:简书博主>齐舞647的创作。此篇文章所有版权归原作者所有,与本公众号无关,商业转载请联系作者获得授权,非商业转载请注明出处。
