MySQL 基础技术(三)—— MySQL 如何保证数据不丢失?

一、引子

上篇:《MySQL 是如何更新的?》中,我们介绍了MySQL的两个重要的日志:binlogredo log

其中,binlog 对应 MySQLServer 层的逻辑日志。
redo log 对应 MySQLInndoDB 引擎下的 Engine 层物理日志。
为了保证数据的一致性, MySQL 用一个事务将两个日志的写逻辑的一致性。(即“两阶段提交”)

MySQL 的两阶段提交 + WAL技术(Write-Ahead Logging,先写日志再写盘),这两个结合在一起保证了数据不会丢失。

即:只要 redo logbinlog 持久化到磁盘,即使 MySQL 异常,重启后数据依然可以恢复。
那么,redo logbinlog 的写入流程具体是怎样的?具体是怎么保证的?

本篇,让我们一起详细了解下 redo logbinlog 的写入流程。


二、binlog 写入流程

首先,我们先了解下MySQL Server 层日志 —— binlog 的写入具体流程。

其实,binlog 的写入流程比较简单:

  1. 先把 binlog 日志写到 binlog cache
  2. 事务提交时,将 binlog cache 写入 binlog 文件。

系统给每个线程的 binlog cache 分配了一片内存,大小为 binlog_cache_size
如果超过这个大小,就会把cache暂存到磁盘的临时文件(tmp files)中。

这个很好理解,binlog cache 由内存和临时文件组件。
因为内存是有限的,在事务完成前不能直接写入最终日志文件,因此只能先临时写到临时文件(tmp files)中。

我们可以看到,每个线程都有自己的 binlog cache。但是,都共用同一份binlog文件。

write:

把日志写入文件系统内核的 page cachepage cache是 OS 对磁盘IO的缓存。

适合小文件传输。因为大文件传输 page cache 命中率低,这时不仅没有起到缓存作用,反而增加了一次数据从磁盘 buffer 到 内核 page cache 的开销。

fsync:

将数据持久化到磁盘。

fsync 才占磁盘的 IOPS(Input/Output Operations Per Second)

MySQL 有一个参数:sync_binlog ,用来控制 writefsync 的时机。
可以根据业务场景的需要,来具体调整。

sync_binlog含义
等于 0每次事务提交只write,不fsync。(不推荐)
等于 1每次事务提交不仅write,都会执行 fsync。(这个配置是最安全的,不会丢binlog日志)
等于 N每次提交事务都write,累积N个事务后,一起fsync。(性能好,但是异常重启会丢N个事务的binlog日志)

三、redo log 写入流程

了解了 binlog 的写入流程,下面我们来看看 redo log 的写入流程。

  1. 首先,写入 redo log buffer
  2. 其次,写入(write)文件系统的 page cache
  3. 最后,持久化(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 + fsyncdisk
= 1每次事务提交时,都将所有 redo log fsync到磁盘。(最安全)
= 2每次事务提交时,都将 redo log writepage 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 logbuffer 中。
    事务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的优势在于:

  1. redo log & binlog 都是按顺序写入磁盘的,比随机写磁盘速度快。
  2. 组提交机制,合并fsync。大幅度降低磁盘的IOPS消耗,提高IO性能。

证明数据安全

上图分为五个阶段,这样,我们在双“1”配置下,利用反证法来验证数据安全性。

  1. 如果 MySQL 挂在了 1,2,3 阶段。

这时候,不论redo log 还是 binlog 都还没有 fsync 到磁盘。
因此,掉电导致内存丢失,实际也没写入磁盘,数据一致性。

  1. 如果 MySQL 挂在了第 4 阶段 fsync binlog
    redo log fsync 成功, binlog fsync 失败。
    MySQL重启后,发现有 redo log 的磁盘数据,没有binlog磁盘数据。
    发现是redo log处于prepare阶段,回滚(删除磁盘里的redo log)。

  2. 如果 MySQL 挂在了第 5 阶段 commit
    redo logbinlogfsync 成功,但 commit 失败。
    MySQL重启后,发现有完整的binlogredo log,继续 commit
    数据成功写入MySQL

至此,我们理解了 MySQL 是如何保证数据不丢失的。
下篇,我们会介绍,MySQL是如何保证高可用的。


链接:https://www.jianshu.com/p/c5754a4edcfa
 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值