mysql 是如何保证 crash-safe 的

关于这个问题,我想你面试的时候会经常被问到,死记硬背是不行的,因为这个问题可以延展出更多的问题来,因此必须真正理解了才行。

所谓的 crash-safe,也就是说再遇到极端情况,比如机器重启,数据仍然不丢失。

我相信你肯定遇到过这种情况,在写 word 的过程中,电脑突然卡死,或者 word 突然崩溃,然后再打开时发现自己辛辛苦苦码的文字已经消失不见了。这就不是 crash-safe。

遇到这种情况怎么解决呢? 假如 word 没 1 秒钟自动保存一次,那么出现电脑突然卡死,或者 word 突然崩溃的情况时,最多损失 1 秒内编辑的文字,就解决了这个问题。

mysql 的逻辑跟这个差不多,但是要更复杂一些。

mysql 的存储引擎基本上都用 InnoDB,因为它支持事务,以这个引擎为例说明:

InnoDB 有个日志文件叫重做日志,redo log,就可以持久化存在磁盘上的,但是在内存中也有一份对应的缓冲区,叫 redo log buffer,为了应对异常重启,InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。

也就是 redo log buffer -> page cache -> 磁盘 这一过程,每秒都在进行,一旦发生异常重启,从 redo log 中恢复就可以了。那具体是怎么恢复的呢?

事务提交之前,先写入 redo log,状态是 prepare,表示已经准备好了,随时可以提交。

事务提交之后,redo log 对应的状态是 commit,表示已经提交。

如果是 prepare 时发生异常重启,mysql 在恢复后对状态为 prepare 状态的事务进行回滚。

如果是 commit 状态,表示本来已经写完了,重启也没关系。

如果是 prepare 之前崩溃了,也无所谓,本来就没有开始写数据,重启也没有任何损失。

现在有了 redo log,只能保证数据不丢,但还无法保证数据可以恢复到之前的某一时刻的状态。这就需要 binlog,binlog 是 mysql 自带的归档日志。

假如在写 binlog 前异常重启,mysql 在恢复后对状态为 prepare 状态的事务进行回滚。

假如在写 binlog 后异常重启,则判断对应的事务 binlog 是否存在并完整:

  • a. 如果是,则提交事务;
  • b. 否则,回滚事务。

你可能会问,处于 prepare 阶段的 redo log 加上完整 binlog,重启就能恢复,MySQL 为什么要这么设计?

回答: binlog 写完以后 MySQL 发生崩溃,这时候 binlog 已经写入了,之后就会被从库(或者用这个 binlog 恢复出来的库)使用。所以,在主库上也要提交这个事务。采用这个策略,主库和备库的数据就保证了一致性。

还有一个问题,就是为什么不让 redo log 也承担 binlog 的功能?

这是因为,redo log 是循环写的,写完后会从开头继续写,这样 redo log 就无法记录一段时间内的完整操作,这样历史日志没法保留,redo log 也就起不到归档的作用。

另一个原因就是就是 MySQL 系统依赖于 binlog。binlog 作为 MySQL 一开始就有的功能,被用在了很多地方。其中,MySQL 系统高可用的基础,就是 binlog 复制。还有很多公司有异构系统(比如一些数据分析系统),这些系统就靠消费 MySQL 的 binlog 来更新自己的数据。关掉 binlog 的话,这些下游系统就没法输入了。

如有问题欢迎留言讨论。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章