MySQL - 几种 log 的深入解析
前言
本篇文章已收录到 GitHub 仓库 https://github.com/logerJava/loger
前面在 MySQL基础概念 中我们有提到几种日志, 本篇文章就来详细了解以下 MySQL 的几种 log, 可能大家会困惑, log 应该只是程序运行的记录, 应该没有什么必要学习, 那么 loger 就出几道 MySQL 数据库的面试题, 我们来带着疑问一起看一下 log 到底在 MySQL 中干了什么 ?
了解过 MySQL 的 ACID, 那么你知道 ACID 在 MySQL 中是怎么实现的吗 ?
MySQL 在写入数据时, 先写入内存, 再写入磁盘, 那么如果在内存数据还未同步到磁盘时掉电了, MySQL 会怎么处理 ?
在工作中一般怎么确认生产环境的慢 SQL ?
常见的 log
在 MySQL基础概念 loger 说认为有用的只有四种日志, 但是本篇文章既然是讲解日志, 那么肯定不能只讲四种, 下面是 MySQL 的七种日志, 我会有侧重的逐一分析
重做日志 (redo log)
回滚日志 (undo log)
二进制日志 (binlog)
错误日志 (errorlog)
慢查询日志 (slow log)
一般查询日志 (general log)
中继日志 (relay log)
redo log
1
redo log 的概念
在了解 redo log 之前我们先要补充一个概念知识 :
MySQL 是按页为单位来读取数据的,一个页里面有很多行记录,从内存刷数据到磁盘,也是以页为单位来刷
现在我们考虑一个场景, 在数据库执行如下 SQL 语句 :
UPDATE student SET name = 'loger' WHERE studentId = 3
在执行这条 SQL 语句时, MySQL 的操作如下 :
首先判断内存中有没有 studentId = 3 的数据
如果没有就去磁盘找到这条数据所在的页, 将整页数据加载到内存
然后找到 studentId = 3 的行数据, 将内存中的 name 修改为 loger
此时, 就会发现数据存在不一致的情况, 内存中的数据为正确数据, 修改过后的新的数据, 而磁盘中的数据为未修改的旧的数据, 这时磁盘对应的页的数据称为 脏页
回到上面提到的第二题, 此时如果掉电了, 怎么办 ? 掉电了数据就没了, 那数据不就不一致了
基于这种场景, MySQL 的解决方案是, 将你对页的操作, 修改了什么内容, 记录下来, 保存到磁盘, 也就是这里要讲的 redo log 中
在 redo log 写入成功后, MySQL 就认为事务已经提交成功了, 数据已经持久化, 并会在空闲时间, 将内存数据刷入磁盘
如果此时掉电, 那么解决起来也简单了, 只需要在重启后将 脏页 数据加载到内存中, 然后利用 redo log 脏页就会修正了
没错, 我已经猜到了, 伙伴们, 你一定会独立思考然后提出这个问题, 如果我写入内存成功了, 但是 redo log 刷到磁盘失败了呢 ? 这个疑问先留着一会解决
2
redo log 如何存储
我们可以查阅 MySQL 的官方文档 :
我们可以大致分析出如下几个点 :
redo log 记录了 SQL 语句以及其他 API 对表产生的变化, 也就是物理变化
redo log 存储在磁盘, 用于 crash recovery 后修正数据, 也就是处理宕机, 掉电等问题
redo log 默认有两个文件
redo log 采取循环写方式 (circular)
也就是说实际上 redo log, 默认是在 ib_logfile0 和 ib_logfile1 循环来回写的, 那么既然用了默认这个词肯定就有下文了, 冷知识讲解 :
innodb_log_file_size:设置每个 redo log 文件的大小,默认是 50331648 byte,也就是 48 MB
innodb_log_files_in_group:设置 redo log 文件的数量,默认是 2,最大值是 100
还有一件事, redo log 在写入磁盘时并不是随机 I/O 的, 而是顺序 I/O 所以写入速度很快, 而 redo log 文件体积又很小, 恢复速度很快
来, 现在回手掏一下, 事务有 ACID 的四个特性, 那么回顾上面讲的 redo log, InnoDB 就是利用 redo log 来实现持久性的
binlog
1
binlog 的概念
还是上面的例子 :
UPDATE student SET name = 'loger' WHERE studentId = 3
在执行这条 SQL 语句时, 其实仅仅生成了 redo log, 还生成了 binlog
binlog 的概念 : binlog 记录了数据库表结构和表数据变更, 比如 insert, delete, update, create 等, 不会记录查询
不同于 redo log, binlog 是 MySQL Server 层的功能, 而 redo log 则是 InnoDB 存储引擎的功能, 也就是说, redo log 只要使用了 InnoDB 作为存储引擎就会有, 而 binlog 只要使用的是 MySQL 就会有
2
binlog 的作用
MySQL 既然将 binlog 放在了 Server 层, 那么就代表 binlog 提供了通用的能力, 主要有两个作用数据恢复, 主从复制
想一想你们 DBA 平时是怎么恢复数据, 还原数据的, 我们回到 binlog, 是不是只需要找到前一时间点的 binlog 重放一下就可以还原了
在主从复制的场景也是同一个原理, master 将 binlog 发送给 slave, slave 执行 binlog 那么就复制了
redo log 与 binlog
通过上面的分析, 我们已经了解了这两种 log, 大家会发现, redo log 和 binlog 都可以作为恢复手段, 但其实他们的细节部分还是不一样的, 我们来看一下他们的区别
1
存储内容不同
binlog 记录的是 insert, delete, update, create 等 SQL 语句, 而 redo log 记录的是物理修改内容
可以理解为, binlog 记录的是逻辑变化, redo log 记录的是物理变化
2
功能不同
redo log 写入内存, 如果数据库宕了, 我们可以通过 redo log 恢复内存还没有刷盘的数据, 可以恢复宕掉之前的内存数据
binlog 可以保持主从一致性, 如果整个数据库都被删除了, binlog 存储着所有数据的变更情况, 可以通过回放 binlog 进行恢复
需要注意的是, 如果整个数据库都被删除了, redo log 是无法恢复的, 因为 redo log 并不会记录历史的所有数据, 文件内容会被覆盖
3
写入细节
刚才有提到 redo log 和 binlog 都会在执行 update 的时候写入, 那么他应该是怎么写入的呢 ?
写入 : redo log (prepare)
写入 : binlog
写入 : redo log(commit)
为什么写入 redo log 需要两段式提交, 而不是一次性写入呢 ? 我们可以分为两种情况分析一下
先写入 redo log, 再写入 binlog
如果 redo log 写入成功, 写入 binlog 失败了, 此时出现宕机情况, 我们根据上面的知识, master 库采用 redo log 恢复数据, 但是因为 binlog 写入失败了, 没有 binlog, 那么从库就没有这些数据, 导致主从不一致现象
先写入 binlog, 再写入 redo log
与上面的类似, 只不过会反过来, binlog 存在而 redo log 不存在, 会导致从库的数据是最新的, 而主库出现问题
两段式提交 :
先写入 redo log, 如果失败了, 回滚, 不再继续写入 binlog
如果 redo log 写入成功, binlog 写入失败, 回滚, 删除无效的 binlog
只有当 redo log 和 binlog 都写入成功了, 此次事务才会提交
也就是说, MySQL 需要保证 redo log 和 binlog 是一致的
undo log
undo log 主要有两个作用 : 回滚, 多版本控制(MVCC)
这里简单讲解一下 undo log 的作用, 详细的会放到 MVCC 篇去讲解, 因为多版本控制其实还是挺复杂的, 要讲很多东西
undo log 实际上存储的也是逻辑日志, 在执行 update 时, 不仅记录上面的两个日志, undo log 也会进行记录, 可以这样理解, 比如你进行了插入操作, 那么 undo log 就会记录一条删除操作, 如果你将 A 修改为了 B, 那么 undo log 里面就会记录一条 B 修改为 A 的记录
目的就是为了保证回滚, 这些 undo log 存储的记录就相当于之前 SQL 前的一个版本, 回滚时直接返回上一个版本, 就一点毛病没有
errorlog
从名字就可以看出来, 是错误日志, 记录着 MySQL 启动, 运行期间, 停止时的错误相关信息, 默认情况下这个是关闭的
我们可以指定 errorlog 的输出路径 :
编辑 my.cnf 写入 log-error = [path]
通过命令参数错误日志 mysqld_safe –user=mysql –log-error=[path] &
general query log
普通查询日志, 记录了 MySQL 接收到的所有查询或命令操作, 无论是正确还是错误的都会进行记录, 因为记录的比较频繁, 产生开销较大所以默认是关闭的
slow query log
慢查询日志记录的是执行时间超过 long_query_time 和没有使用索引的查询语句, 只记录成功的语句
相关参数 :
slow_query_log : 1. 开启; 0. 关闭
long_query_time : 慢查询阈值
log_output : 输出方式
我们可以通过如下方式配置慢查询 :
show variables like '%slow_query_log%';
set global slow_query_log=1;
show variables like '%slow_query_log%';
要注意的是, 此处修改只对当前数据库生效, 在 MySQL 重启后会失效, 如果需要配置永久生效需要修改 my.cnf 文件
参考
MySQL 是如何实现 ACID 中的 D 的?- 柳树
https://zhuanlan.zhihu.com/p/98778890
结尾
相信看了上面的各种日志讲解, 大家已经对前言中提出的问题有了答案, 本篇文章讲解了 MySQL 的 log 问题, 希望大家有所收获
我是 loger, 扫描下方二维码, 更多程序员知识分享等你来看, 每次打开都有新收获, 不妨来关注一下哦, 兄弟们 👍