之前,有一年多的工作客户端领域的工作经验。
后来,也在字节做了一年多的后端业务。
现在希望做一些MySQL
总结,丰富一下自己在后端领域的积累。
目录如下:
MySQL 基础技术(一) —— MySQL 是如何查询的?
MySQL 基础技术(二) —— MySQL 是如何更新的?
MySQL 基础技术(三)—— MySQL 如何保证数据不丢失?
MySQL 基础技术(四)—— MySQL 如何保证高可用?
一、引子
上篇:《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 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 log
fsync
到磁盘 - 写 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
。
至此,我们理解了 MySQL
是如何保证数据不丢失的。
下篇,我们会介绍,MySQL
是如何保证高可用的。
参考与致谢:
1.《MySQL实战45讲》(林晓斌老师)