MySQL 日志全景图:一次 SQL 到底会产生哪些日志?

0 阅读4分钟

先走一遍完整的更新流程

日志分散在不同的层里,拿这条语句做例子,id 是主键,执行前这一行的 name'a'

UPDATE t SET name = 'b' WHERE id = 1;

从客户端发出去到最终落盘,中间是这些步骤:

Server 层
  连接器     校验账号密码和权限
  分析器     词法语法分析,知道这是一条 UPDATE,表是 t,条件是 id = 1
  优化器     决定走主键索引
  执行器     调用 InnoDB 的读写接口

InnoDB 层
  1. 从 buffer pool 里找到 id = 1 这一行,页不在内存就从磁盘读进来
  2. 写 undo log,记下"这一行的 name 原来是 'a'"
  3. 修改 buffer pool 里这一行,页变成脏页
  4. 写 redo log,记下"在某个页的某个位置把 name 改成了 'b'"
     注意这个动作在执行过程中就在做,先进 redo log buffer

提交
  5. redo log 按 innodb_flush_log_at_trx_commit 的策略刷盘
  6. binlog 从 binlog cache 写进文件,按 sync_binlog 的策略刷盘

两个时间点要分清楚:redo log 是事务执行过程中一直在写的,binlog 是提交的那一刻才落盘的。第 4 步只写进内存,第 5 步才是真正决定"这个事务丢不丢"的地方。

几种日志的对照

日志归属记的是什么干什么用
redo logInnoDB 引擎层物理逻辑日志,对数据页做了什么修改崩溃恢复
undo logInnoDB 引擎层逻辑日志,修改前的旧值或反向操作事务回滚、MVCC 快照读
binlogServer 层逻辑日志,语句或行的变更主从复制、按时间点恢复
relay logServer 层(从库)从主库拉过来的 binlog从库回放
error logServer 层错误、警告、启停信息排障
slow query logServer 层超过 long_query_time 的语句性能分析
general logServer 层所有语句调试,默认关闭

前面三个是重点,后面几个是运维用的,和事务、崩溃恢复没关系。


三组容易混的关系

redo log 和 binlog

这两个是最常被放在一起问的,从四个维度对比:

维度redo logbinlog
归属InnoDB 引擎层,MyISAM 就没有Server 层,所有引擎都有
内容物理逻辑日志,对数据页的修改逻辑日志,语句或行的变更
写入方式循环写,空间固定,写满就覆盖追加写,写完一个文件换下一个,不覆盖
何时写事务执行过程中持续写事务提交时才刷到文件
用途崩溃恢复主从复制、按时间点恢复

一句话概括:redo log 保证"已经提交的事务不丢",binlog 保证"数据能被复制和重放"

redo log 和 undo log

一个往前,一个往后:

  • redo log 是前滚,崩溃恢复的时候把已提交但没落盘的修改重做一遍
  • undo log 是回滚,事务失败或者崩溃恢复的时候把不该留的修改撤掉

redo 记的是"要做什么",undo 记的是"怎么撤回去"。两者的细节分别在 Redo Log:MySQL 为什么能够实现崩溃恢复?和 Undo Log:事务回滚是怎么实现的?两篇里展开。

为什么一定要 WAL

反过来想:如果不写 redo log,事务提交的时候直接把数据页刷回磁盘,会怎样?

数据页是 16KB,改一行也得把整页写回去;页在磁盘上的分布是随机的,写一次就是一次随机 IO;一个事务改了好几个页,就要好几次随机 IO 才能提交。磁盘的随机 IO 性能比顺序写差一两个数量级,这个代价扛不住。

WAL(Write-Ahead Logging,预写日志)换了个方向:先顺序写日志,数据页交给后台慢慢刷。写日志是追加,快;数据页攒一批再刷,多次随机写能合并成一次。

代价就是数据页会落后于日志。这个"落后"正是 redo log 存在的理由,也是下面一篇要讲的东西。


不同的语句产生的日志不一样

回到标题这个问题,按语句类型分:

语句undo logredo logbinlog
SELECT不写不写不写
INSERT写,记的是"删掉这一行"
UPDATE写,记旧值
DELETE写,记整行内容

三种写操作里,INSERT 的 undo log 稍微特别一点:它不需要记旧值(原来根本没有这一行),记的是主键,回滚的时候按主键把这行删掉。所以 undo log 记的不一定是"旧值",准确说是"反向操作"。

SELECT 不产生这三种日志,但它不是完全不碰日志。一个跑了很久的 SELECT 会一直读某个版本的 undo log,只要它还没结束,这个版本就不能被清理,undo 表空间会一直涨。这属于 MVCC 的话题,可以回顾一下 MVCC 的版本链。

DDL 语句(ALTER TABLE 这类)不写 undo log,因为 DDL 不能回滚,它的日志行为和上面这套是分开的,不在这里展开。