详解 MySQL 日志系统之 redo logbinlog
Posted CodeFish
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解 MySQL 日志系统之 redo logbinlog相关的知识,希望对你有一定的参考价值。
一、基础架构
mysql 可以分为 Server 层和存储引擎层两部分:
1. Server 层:涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
2. 存储引擎层:负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、 Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从MySQL 5.5.5 版本开始成为了 默认存储引擎。
一条查询 SQL 语句在 MySQL 的各个功能模块中的执行过程如下图所示:
在整个执行流程中,每个组件的作用如下:
1. 连接器:
连接器负责跟客户端建立连接、获取权限、维持和管理连接。
2. 查询缓存:
MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。
之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。
如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。
如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。
需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了。
3. 分析器:
分析器先会做词法分析。识别出一条 SQL 语句里面的字符串分别是什么,代表什么。
根据词法分析的结果,语法分析器会根据语法规则, 判断该 SQL 语句是否满足 MySQL 语法。
4. 优化器:
优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(JOIN) 的时候,决定各个表的连接顺序。
5. 执行器:
首先判断用户对这个表有没有执行查询的权限,如果没有,就会返回错误。
如果有权限,就打开表继续执行。执行器会根据表的引擎定义,去使用这个引擎提供的接口。
二、日志系统
前面我们了解一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块。
那么,一条更新语句的执行流程又是怎样的呢?
可以确定的说,查询语句的那一套流程,更新语句也是同样会走一遍。
但是与查询流程不一样的是,更新流程还涉及两个重要的日志模块,它们正是我们今天要讨论的主角:redo log(重做日志)和 binlog(归档日志)。
2.1 redo log
如果 MySQL 每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。
为了解决这个问题,MySQL 使用了 WAL(Write Ahead Logging) 技术,它的关键点就是先写日志,再写磁盘。
具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log 里面,并更新内存,这个时候更新就算完成了。
同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。
InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么 redo log 总共就可以记录 4GB 的操作。
从头开始写,写到末尾就又回到开头循环写,如下图所示:
write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。
write pos 和 checkpoint 之间的部分可以用来记录新的操作。如果write pos 追上checkpoint,表示记录满了,这时候不能再执行新的更新,得停下来先更新一些记录到磁盘,把 checkpoint 推进一下。
有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。
2.2 binlog
上面我们聊到的 redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。
为什么会有两份日志呢?
因为最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。
而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力。
这两种日志有以下三点不同:
1.redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。2.redo log 是物理日志,记录的是 “在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如 “给 id = 2 这一行的 c 字段加 1”。3.redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写” 是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
有了对这两个日志的概念性理解,我们再来看执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程。
UPDATE t SET c = c + 1 WHERE id = 2;
图中粉色框表示是在外部执行器中执行的,黄色框表示是在 InnoDB 引擎内部执行的。
你可能注意到了,最后三步将 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是 “两阶段提交”。
2.3 两阶段提交
为什么必须有 “两阶段提交” 呢?
2.3.1 保证逻辑一致
这里不妨用反证法来进行解释。
由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。
仍然用前面的 update 语句来做例子。假设当前 id = 2 的行,字段 c 的值是 0,再假设执行 update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?
1. 先写 redo log 后写 binlog: 假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是1。但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。
2. 先写 binlog 后写 redo log: 如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了 “把 c 从 0 改成 1” 这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。
可以看到,如果不使用 “两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。
2.3.2 保证数据完整性
在两阶段提交的不同时刻,MySQL 异常重启会出现什么现象?
如果在图中 时刻A 的地方发生了 crash,由于此时 binlog 还没写,redo log 也还没提交,所以崩溃恢复的时候,这个事务会回滚。这时候,由于 binlog 还没写,所以也不会传到备库。
如果在图中 时刻B 的地方发生了 crash,恢复时会进行一次判断:
•如果 redo log 里面的事务是完整的,也就是已经有了 commit 标识,则直接提交;•如果 redo log 里面的事务只有完整的 prepare,则判断对应的事务 binlog 是否存在并完整:a. 如果是,则提交事务;b. 否则,回滚事务。
这里,时刻B 发生 crash 对应的就是 2(a) 的情况,崩溃恢复过程中事务会被提交。
2.4 问题延申
2.4.1 redo log 和 binlog 是怎么关联起来的?
它们有一个共同的数据字段,叫做 XID。崩溃恢复的时候,会按顺序扫描 redo log:
•如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;•如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务。
2.4.2 正常运行中的实例,数据写入后的最终落盘,是从 redo log 更新过来的还是从 buffer pool 更新过来的呢?
实际上,redo log 并没有记录数据页的完整数据,所以它并没有能力自己去更新磁盘数据页,也就不存在 “数据最终落盘,是由 redo log 更新过去” 的情况。
1.如果是正常运行的实例的话,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。最终数据落盘,就是把内存中的数据页写入磁盘。这个过程,甚至与 redo log 毫无关系。2.在崩溃恢复场景中,InnoDB 如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让 redo log 更新内存内容。更新完成后,内存页变成脏页,就回到了第一种情况的状态。
2.4.3 redo log buffer 是什么?是先修改内存,还是先写 redo log 文件?
在一个事务的更新过程中,日志是要写多次的。比如下面这个事务:
BEGIN;
INSERT INTO t1 ...
INSERT INTO t2 ...
COMMIT;
这个事务要往两个表中插入记录,插入数据的过程中,生成的日志都得先保存起来,但又不能在还没 commit 的时候就直接写到 redo log 文件里。
所以,redo log buffer 就是一块内存,用来暂存 redo 日志的。也就是说,在执行第一个 insert 的时候,数据的内存被修改了,redo log buffer 也写入了日志。
但是,真正把日志写到 redo log 文件(文件名是 ib_logfile + 数字),是在执行 commit 语句的时候做的。
写在最后:
想要继续深入学习 MySQL 吗?扫描下方二维码或关注微信公众号:CodeFish 回复关键字 “电子书” 即可获取相关书籍资料!
更多优质电子书资源持续更新中...
以上是关于详解 MySQL 日志系统之 redo logbinlog的主要内容,如果未能解决你的问题,请参考以下文章
图文结合带你搞懂MySQL日志之Redo Log(重做日志)
图文结合带你搞懂MySQL日志之Redo Log(重做日志)
MySQL系列之日志汇总:redo logundo logbinlogerrorlogslow query loggeneral logrelay log