一、简介

MySQL 保证数据不会丢的能力主要体现在两方面:

  1. 能够恢复到任何时间点的状态;(通过全量备份+binlog)
  2. 能够保证MySQL在任何时间段突然奔溃,重启后之前提交的记录都不会丢失;(crash-safe:通过redo log+undo log )

因为crash-safe主要体现在事务执行过程中突然奔溃,重启后能保证事务完整性,所以在讲解具体原理之前,先了解下MySQL事务执行有哪些关键阶段,后面才能依据这几个阶段来进行解析。下面以一条更新语句的执行流程

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/a80b7fd2-877d-487c-a8e0-4cc5dcecda89/Untitled.png

从上图可以清晰地看出一条更新语句在MySQL中是怎么执行的,简单进行总结一下:

  1. 从内存中找出这条数据记录,对其进行更新;
  2. 将对数据页的更改记录到redo log中;
  3. 将逻辑操作记录到binlog中;
  4. 对于内存中的数据和日志,都是由后台线程,当触发到落盘规则后再异步进行刷盘;

上面演示了一条更新语句的详细执行过程,接下来咱们通过解答问题,带着问题来剖析这个crash-safe的设计原理。

二、WAL机制

为什么不直接更改磁盘中的数据,而要在内存中更改,然后还需要写日志,最后再落盘这么复杂?

这个问题相信很多同学都能猜出来,MySQL更改数据的时候,之所以不直接写磁盘文件中的数据,最主要就是性能问题。因为直接写磁盘文件是随机写,开销大性能低,没办法满足MySQL的性能要求。所以才会设计成先在内存中对数据进行更改,再异步落盘。但是内存总是不可靠,万一断电重启,还没来得及落盘的内存数据就会丢失,所以还需要加上写日志这个步骤,万一断电重启,还能通过日志中的记录进行恢复。

写日志虽然也是写磁盘,但是它是顺序写,相比随机写开销更小,能提升语句执行的性能(针对顺序写为什么比随机写更快,可以比喻为你有一个本子,按照顺序一页一页写肯定比写一个字都要找到对应页写快得多)。

这个技术就是大多数存储系统基本都会用的WAL(Write Ahead Log)技术,也称为日志先行的技术,指的是对数据文件进行修改前,必须将修改先记录日志。保证了数据一致性和持久性,并且提升语句执行性能。

三、核心日志模块

问题:更新SQL语句执行流程中,总共需要写3个日志,这3个是不是都需要,能不能进行简化?

更新SQL执行过程中,总共涉及**MySQL日志模块其中的三个核心日志,分别是redo log(重做日志)、undo log(回滚日志)、binlog(归档日志)。**这里提前预告,crash-safe的能力主要依赖的就是这三大日志。

接下来,针对每个日志将单独介绍各自的作用,然后再来评估是否能简化掉。

1、重做日志 redo log

redo log 也称为事务日志,由 InnoDB 存储引擎层产生。记录的是数据库中每个页的修改,而不是某一行或某几行修改成怎样,可以用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置,因为修改会覆盖之前的)。

前面提到的WAL技术,redo log就是WAL的典型应用,MySQL在有事务提交对数据进行更改时,只会在内存中修改对应的数据页和记录redo log日志,完成后即表示事务提交成功,至于磁盘数据文件的更新则由后台线程异步处理。由于redo log的加入,保证了MySQL数据一致性和持久性(即使数据刷盘之前MySQL奔溃了,重启后仍然能通过redo log里的更改记录进行重放,重新刷盘),此外还能提升语句的执行性能(写redo log是顺序写,相比于更新数据文件的随机写,日志的写入开销更小,能显著提升语句的执行性能,提高并发量),由此可见redo log是必不可少的。

redo log是固定大小的,所以只能循环写,从头开始写,写到末尾就又回到开头,相当于一个环形。当日志写满了,就需要对旧的记录进行擦除,但在擦除之前,需要确保这些要被擦除记录对应在内存中的数据页都已经刷到磁盘中了。在redo log满了到擦除旧记录腾出新空间这段期间,是不能再接收新的更新请求,所以有可能会导致MySQL卡顿。(所以针对并发量大的系统,适当设置redo log的文件大小非常重要!!!)