前言

本章的线索 —— 一条 MySQL 的更新语句,通过这条语句引出一个 MySQL 中非常重要的模块 —— 日志
日志又分为两部分:

  • Server 层的逻辑日志 —— binlog
  • InnoDB 存储引擎独有的物理日志 —— redo log

但是我作为一个之前对 MySQL 的日志毫无了解的人来说,感觉光看这篇文章并没有办法学完全貌,但是作为一个线索去学习是没有问题的,下面正式开始吧。

一条贯穿始终的 MySQL Update 语句

// 先创建表
mysql> create table T(ID int primary key, c int);

// 执行更新语句
mysql> update T set c=c+1 where ID=2;

这里作者突然又提到了一个点: MySQL 可以恢复到半个月内任意一秒的状态 ,这个点提的有点突兀,最起码我是没看出怎么引出这个的。
根据这个"时间回溯"的能力,作者引出了 MySQL 的日志 这个点。

这里回顾了一下 MySQL执行链路,以及各个组件的对应工作,如下图:

这里更新语句和上一章的查询语句有一个关键的区别:

  • MySQL 的更新流程涉及到了 MySQL 的两个日志模块:
    • 存储引擎redo log
    • Server 层bin log

下面作者就开始逐一讲解这两个日志模块,通过一个酒店记账的例子将下面的知识点讲解串起来。

引擎层日志 redo log

个人 Questions:

  • 1、什么是 redo log?
  • 2、redo log 的相关特点?

这里作者举了个《孔乙己》 中酒店老板记录赊账信息的例子:
对于赊账信息有两种记录方法:

  • 第一种:记录在店中的白板上。优点是速度快,缺点显而易见 —— 空间有限。
  • 第二章:记录在账本里,优点是存储空间够大,缺点就是找到对应赊账人的信息速度慢。

那么怎样平衡这两种记账方式:

  • 当酒店里生意红火人多的时候,先将赊账记录写到粉板上,等少了之后再摘抄到账本中

这个例子对应着 MySQL 更新时的场景,如果每次更新都直接操作磁盘中 对应的记录,那么速度肯定很慢:

  • 粉板代表着 内存 速度快,但是存储空间相对小
  • 账本则是 磁盘 ,速度慢,但是存储空间大

这里 MySQL 的设计者就用到了上面描述的 "酒店、掌柜、粉板" 这个思路来设计 MySQL 的日志,以提升效率:
WAL 技术: Write-Ahead Logging :

  • 先将更新写入日志,再将日志写入磁盘

其实这也包含了批量处理的思想,具体描述这个过程就是:

  • 1、当一条记录需要被更新时, InnoDB 引擎将记录写入 redo log,并更新内存中的值,此时更新操作完成
  • 2、引擎会在系统比较空闲的时候将操作记录更新到磁盘中。

那么一个问题随之而来:内存被写满之后的处理逻辑是什么?

  • 将内存中的日志持久化到磁盘中

InnoDB redo log 大小固定,可以将其配置为 4个文件,每个文件大小 1GB ,那么总大小就是 4GB,对应的具体操作时:

  • 4个文件呈换装,从头开始写,写到末尾再到头部循环写入,示意图如下。

下面的是我总结的 redo log 的一些特点,但是比较凌乱:
image-20200920141132972

但是这里作者给出的信息,我不能全部无条件接受,必须得自己去 MySQL 中进行验证,所以增加了对应的实验环节。

redo log 中的关键概念

write pos

  • 当前记录的位置,该记录会随着日志的写入不断后移。如图中,当写满3号文件之后指针就回到了 1号文件,是循环写入的。 【这么设计的意义是什么呢?为什么要设计成一个圈,循环写入这样】

checkpoint

  • 当前要擦除的位置,其行为逻辑也是 不断后移,且循环

可用区域: 写入点 write pos 和 检查点 check point 之间的区域。

当 write pos 和 checkpoint 重叠时,代表写入区域用完了,此时需要将日志持久化到磁盘中。

redo log 的作用

这里作者直接说 redo log 让 InnoDB 在 MySQL 崩溃时也不会丢失数据,也就是 crash-safe 能力。

  • 当 crash 发生时,MySQL 可以通过 redo log 知道哪些数据还没有写入磁盘,所以就不会丢失提交操作了。

binlog

bin log : MySQL Server 层的逻辑日志。
作用:记录操作,可以将 MySQL 恢复到任意时间点的状态就是根据 bin log 的能力来完成的。

bin log 和 redo log 的区别:

区别日志层级不同日志类型日志行为不同
redo logInnoDB 特有的日志系统redo log物理日志,记录的是"在某个数据上做了什么修改"redo log 循环写,空间固定会用完
binlogServer层实现的日志,所有引擎都可以使用binlog逻辑日志,记录的是语句的原始逻辑:"给 ID=2 这一行的 c 字段加1"binlog 可以追加写入:当 binlog 文件写到一定大小后会切换到下一个,不会覆盖之前的日志。

InnoDB 引擎执行 MySQL Update 语句的流程

  • 1、执行器通过接口,调用存储引擎,获取 ID = 2 这一行记录。 由于 ID 是主键存储引擎直接使用树搜索找到这一行,如果这页数据在内存中则直接返回,不在的话需要去磁盘中读取后返回
  • 2、执行器对拿到的数据进行业务操作后调用存储引擎写入新的行数据
  • 3、存储引擎更新数据到内存中,同时写入更新记录到 redo log,此时 redo log 处于 prepare 状态,引擎返回执行器操作执行完成,可以提交事务
  • 4、执行器生成对应的 bin log,将 bin log 写入磁盘
  • 5、执行器调用 存储引擎 的 提交事务接口,存储引擎将刚才的 redo log 改为 commit 提交状态,整个更新流程结束

可以看到,这里 MySQL 并没有让 redo log 直接将更新写入磁盘,而是采用了 两阶段提交这种模式,即:

  • 写入 redo log —— 写入 bin log —— 提交事务

两阶段提交

图示2

MySQL 为什么需要将更新操作设计为两阶段提交?

  • 为了保证 bin log 和 redo log 之间的逻辑一致。

这里根据我的理解,其实就是把写入两份日志的动作封装成一个原子操作,只有在最后 commit 完成之后才会同时写入两份日志。

如果没有两阶段提交:

  • 先写入 redo log ,然后写入 bin log,在写入 redo log 之后,binlog 之前 MySQL Crash

    • redo log 的日志还在,但是 bin log 的逻辑日志没了,而 bin log 的作用之一就是恢复数据,如果创建了一个从库,然后通过 bin log 恢复数据,这里就会少一条数据。
  • 先写入 bin log ,然后写入 redo log, 在写入 bin log 之后,redo log 之前 MySQL Crash

    • 由于 redo log 还没写,崩溃恢复后这个事务无效,本次更新语句也没有成功,但是由于 bin log 逻辑日志已经记录了本次更新语句,所以就导致了如果使用 bin log 恢复数据 或者 恢复从库时 的数据与主库不一致。

redo log 和 bin log 都可以用于表示事务提交的状态,两阶段提交让这2个状态保持一致。

  • redo log 和 bin log 具有关联性
    • 恢复数据时, redolog 用于恢复主机故障时未更新的物理数据,binlog 用于备份操作
    • 恢复数据时,redo log 的状态是 commit 说明整个事务成功,直接进行恢复,如果 redo log 的状态是 prepare ,则需要查询 binlog 事务是否成功,如果成功则执行 redolog 的 commit,否则进行回滚

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

最是人间留不住,曾是惊鸿照影来。