前言
本章的线索 —— 一条 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 的一些特点,但是比较凌乱:
但是这里作者给出的信息,我不能全部无条件接受,必须得自己去 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 log | InnoDB 特有的日志系统 | redo log 是物理日志,记录的是"在某个数据上做了什么修改" | redo log 循环写,空间固定会用完 |
binlog | Server层 实现的日志,所有引擎都可以使用 | 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.
Comments | 0 条评论