1 导言

假如让你来完成一下 MySQL 耐久化的功用,你准备怎么完成?假如不考虑功能,接口彻底运用同步机制完成,好像也不会呈现什么问题,可是不考虑功能是不现实的,IO操作多么耗时,每次写磁盘?那 MySQL 恐怕彻底无法支撑企业级运用。

考虑加缓存,写磁盘之前先写内存,然后异步耐久化,由此带来最棘手的问题,怎么确保数据一致性?

咱们要确保的是,即便数据耐久化变为异步操作,在服务器产生宕机、断电等意外状况时,仍旧能够将数据正确的耐久化——已写入内存的数据不会由于意外状况导致未耐久化(数据丢掉)、异步耐久化数据的进程确保原子性,要么悉数耐久化成功,要么悉数失败,不能写入不完好的数据(极端状况下一个数据由 128 字节组成,但只耐久化了 64 字节)。

来看 MySQL 怎么进步功能、并解决数据一致性的问题。

2 直接写磁盘功率低下解决方案

2.1 InnoDB buffer pool的引进

不同的存储引擎有不同的完成,这儿来看 InnoDB 是怎么添加缓存层的。

MySQL InnoDB Buffer Pool,MySQL InnoDB 缓冲池。里边缓存着很多数据(数据页),使 CPU 读取或写入数据时,不直接和低速的磁盘打交道,直接和缓冲区进行交互,然后解决了由于磁盘功能慢导致的数据库功能差的问题。

数据页:InnoDB 中,数据管理的最小单位为页,默许是 16KB。详见:计算机存储术语:扇区,磁盘块,页。

InnoDB buffer pool详解:MySQL–怎么加速读写速度?详解Buffer Pool。

2.2 数据一致性的保障

在了解了 buffer pool 之后,要确保数据一致性,则要进行刷脏。假如每次写操作都要进行同步刷脏,则功能仍旧会很差,因而一般都会在必定机遇进行批量刷脏(即异步批量刷脏)。这时,要确保的是如安在宕机、断电的状况下,脏页仍旧能够正确刷入磁盘,即InnoDB 的刷脏战略是怎么完成的

2.2.1 Write-Ahead Log机制

InnoDB 刷脏时选用的是WAL(Write Ahead Log)机制,即先写日志,再写入磁盘。InnoDB 经过 redo log 日志完成了 WAL,让 MySQL 拥有了溃散恢复才能。一般来说,任何要完成数据耐久化的体系,都需求选用 Write-Ahead Log 机制。

WAL机制解决了如下问题:

  1. 数据耐久化不受影响(日志自身就保存在磁盘上,写日志成功就意味着数据现已耐久化)
  2. 进步了写功能
  3. 避免了在产生宕机、断点等状况下,给磁盘中写入不完好数据状况的产生

关于次序IO和随机IO我在这儿提一嘴,感觉网上说的都不是很明晰:

磁盘的默许行为是随机IO,次序 IO 需求运用程序或许文件体系进行操控,产生随机 IO 的原因是磁盘上的数据随时在改变(增、删),这样会导致磁盘产生空间碎片(本来1,2,3是在同一磁道次序存储,但删除了 2 后,这部分空间就会被开释,然后可能写入其他数据),随着时刻的推移,本来的次序 IO 就会变成随机 IO。

咱们说的写日志,假如不提前指定日志的巨细,告知磁盘一次性分配多大的接连空间,即便咱们是追加写日志,磁盘实践仍是在做随机 IO。

数据库中的数据自身是无安排的,伴随着增删,终究会演变为随机 IO,而 B+ 树索引是有结构和必定次序的数据,因而能够“粗犷”的了解为往磁盘上读写索引是次序 IO。

其实次序 IO 比随机 IO 快现已是好几年前的说法了,目前由于硬件的进步,像 SSD 等硬盘的呈现,随机 IO 现已不比次序 IO慢多少了。

终究,Java 中有专门操控次序 IO 的 API,感兴趣的能够了解下。

1.关于第二点的阐明——redo log怎么进步写功能?

随机写转次序写:假如没有 redo log,则脏页的改写,从内存刷至磁盘彻底是随机IO,而redo log中记载的是业务的更新内容(比如将主键id=1的数据行,其age字段改为0,是一种行为日志),而非真实的数据,因而,经过 redo log 可将随机的脏页写入变成次序的日志刷盘,真实的脏页改写流程由后台线程去做。

更详细的解释可参考:次序io和随机io分别在什么状况下呈现?为什么日志是次序io,数据是随机io?- 白竹蓝的答复。

2.关于第三点的阐明——redo log怎么确保数据一致性?

假如不考虑 bin log,咱们能够大致了解为 MySQL 写数据的流程是这样的:buffer pool 中更新好数据后,同步写 redo log,redo log 写成功后,当前业务才能提交成功,否则业务失败。剩下的作业,redo log 刷脏,交给异步线程去做即可。由于 redo log 的写入是同步操作,因而 buffer pool 同步至 redo log 确保了数据一致性。由于 redo log 自身现已落盘,因而不管产生宕机仍是断电,MySQL 都有才能从上次消费 redo log 的地方开始接着消费,然后确保了数据的终究一致性(不会产生数据丢掉)。

但事实是,企业级运用中,MySQL 一般都是主从架构,由于 bin log 的存在,因而 redo log 选用了两阶段提交,既确保了主从架构的数据一致性,也确保了本机的 crash-safe,关于 redo log 的两阶段提交,详见MySQL 为什么需求两阶段提交?。

3 详解 redo log

接下来体系的学习一下 redo log。

3.1 redo log简介

InnoDB 引擎独有的日志模块,redo log 通常是物理日志,记载的是数据页的物理修正。

3.2 redo log作用

  1. 进步 MySQL 写操作功率——直接写磁盘是随机写,写 redo log 是次序写
  2. 防止在产生毛病的时刻点,尚有脏页未写入磁盘,在重启 MySQL 服务的时分,根据 redo log 进行重做,然后达到业务的耐久性这一特性

3.3 需求什么样的redo log

redo log 的保护添加了一份写盘数据,写盘时刻会直接影响体系吞吐。显而易见,redo log 的数据量要尽量少。其次,体系溃散总是产生在始料未及的时分,当 MySQL 重启时,体系并不知道 redo log 中哪些 Page 现已落盘,因而 redo 操作要确保幂等。终究,为了便于经过并发的方式加快重启恢复速度,redo 应该是根据 Page 的,即一个 redo 只涉及一个 Page 的修正。

物理日志 vs 逻辑日志

物理日志(physical log)占用一片接连的磁盘空间。其主要目的是为体系进行快速恢复供给原始数据映像。MySQL 中的物理日志更偏向于记载了在某个数据页上做了什么修正。

逻辑日志(logical logs)是由若干块独立的磁盘空间构成,而每一块都是接连的磁盘空间。MySQL 中的逻辑日志更偏向于记载的是一条 SQL 语句的原始逻辑,例如给某一个字段 +1。

3.4 redo log buffer

在了解 redo log 之前,首先要认识一下 redo log buffer。

redo log 包含两部分:一是内存中的重做日志缓冲(redo log buffer),该部分日志是易失的;二是磁盘上的重做日志文件(redo log file),该部分日志是耐久的。

注:下文中所述的“刷到磁盘上”都指刷到 redo log file 中

那么为什么会参加 redo log buffer 呢?

在概念上,innodb 经过 force log at commit 机制完成业务的耐久性,即在业务提交的时分,必须先将该业务的一切业务日志写入到磁盘上的 redo log file 中进行耐久化。

以一个 update 操作为例,来了解 redo log 的更新流程:

MySQL--事务持久化原理探究

综上,在业务提交前就要将 redo log 的内容写入磁盘,而且由于 redo log 记载的是数据库物理页的改变,因而 redo log 文件中一个业务可能有多条记载,终究一个提交的业务记载会掩盖一切未提交的业务记载。

例如业务 T1,可能在 redo log 中记载了 T1-1, T1-2, T1-3, T1* 共 4 个操作,其间 T1* 表示终究提交时的日志记载,所以对应数据页的终究状况是 T1* 对应的操作成果。

假如没有 redo log buffer,每次产生的业务记载都要刷到磁盘上,便会形成磁盘 IO 升高,功能降低,而且也不利于大业务的运行

为了确保每次业务提交后 redo log buffer 中的内容都能写入到 redo log file 中,InnoDB 存储引擎会调用一次操作体系的 fsync 操作(即 fsync() 体系调用)。由于 MySQL 是作业在用户空间的,因而 redo log buffer 处于用户空间的内存中,要写入到磁盘上的 redo log file 中,中心还要经过操作体系内核空间的 os buffer,调用 fsync() 的作用便是将 OS buffer 中的日志刷到磁盘上的 redo log file 中。

从 redo log buffer 写日志到磁盘的 redo log file 中,进程如下:

MySQL--事务持久化原理探究

之所以要经过一层 OS buffer,是由于 open 日志文件的时分,open 没有运用 O_DIRECT 标志位。该标志位意味着绕过操作体系层的 OS buffer,IO 直接写到底层存储设备。不运用该标志位意味着将日志进行缓冲,缓冲到了必定容量,或许显式调用 fsync() 才会将缓冲中的内容刷到存储设备。比如写 abcde,不运用 O_DIRECT 将只建议一次体系调用,运用 O_DIRECT 将建议 5 次体系调用。

MySQL 支撑用户自定义在 commit 时怎么将 redo log buffer 中的日志刷 redo log file 中。这种操控经过变量 innodb_flush_log_at_trx_commit 的值来决定。该变量有 3 种值:0、1、2,默许为 1。但留意,这个变量只是操控 commit 动作是否改写 redo log buffer 到磁盘。

  • 当设置为 1 的时分,业务每次提交时都会将 redo log buffer 中的日志写入 os buffer 并调用 fsync() 刷到 redo log file 中(同步)。这种方式即便体系溃散也不会丢掉任何数据,但是由于每次提交都要写入磁盘,IO 的功能较差

  • 当设置为 0 的时分,业务提交时不会将 redo log buffer 中的日志写入到 os buffer,而是每秒写入 os buffer 并调用 fsync() 写入到 redo log file 中,也便是说设置为 0 时是(大约)每秒改写写入到磁盘中的。这种状况下当 MySQL 进程溃散时,便会丢掉 1 秒钟的数据

  • 当设置为 2 的时分,每次提交都写入到 os buffer,然后每秒调用 fsync() 将 os buffer 中的日志写入到 redo log file。这种状况下只有当操作体系溃散或许体系掉电,才会丢掉 1 秒钟的数据

上面说到的“终究 1s”并不是绝对的,有时会丢掉更多数据。有时由于调度问题,每秒刷写(once-per-second flushing) 并不能确保 100% 履行。关于一些数据一致性和完好性要求不高的运用,配置为 2 就足够了,假如为了最高功能,能够设置为 0;有些运用,如付出服务,对一致性和完好性要求很高,所以即便最慢,也要设置为 1。

3.5 redo log刷脏

详见:redo log 什么时分刷盘?

4 参考阅览

  1. MySQL 日志:undo log、redo log、binlog 有什么用?
  2. 详细分析MySQL业务日志(redo log和undo log)
  3. 『MySQL』深化了解业务的来龙去脉
  4. MySQL参数:innodb_flush_log_at_trx_commit 和 sync_binlog
  5. 庖丁解InnoDB之REDO LOG
  6. MySQL业务和耐久化原理