MySQL 日志模块

By | 13 2 月, 2021

MySQL 里如果每一次的更新操作都写磁盘的话,那么这个过程中磁盘要找到对应的那条记录,然后再更新,整个过程中的 IO 成本、查找成本都很高,所以 MySQL 中采用了 WAL(Write-Ahead Logging)技术来解决这个问题。

关键点是:先写日志,再写磁盘

redo log

redo log 是 InnoDB 引擎特有的日志。

当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log 中,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。

InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写。

有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。

binlog

MySQL 整体来看,其实就两块,一块是 Server 层,主要做 MySQL 功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。上面的 redo log 是 InnoDB 引擎层面的,对应到 Server 层面的日志,则称为 binlog。

binlog 有两种模式,statement 格式的话是记录 SQL 语句;row 格式会记录行的内容,记录两条,分别是更新前的内容和更新后的内容。

update 语句的基本执行流程

下面以一条 update 语句为例描述基本执行流程:

update T set N=N+1 where ID=2;
  1. 执行器先找引擎取 ID=2 这一行。如果这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回
  2. 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据
  3. 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时,注意 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务
  4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘
  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成

这里描述下执行过程中间出现意外的场景。

  1. prepare redo log
  2. write binlog
  3. commit redo log

如果在 2 之前崩溃,那么重启恢复后,InnoDB 发现没有 commit,回滚。备份恢复:没有 binlog。两者保持一致。

如果在 3 之前崩溃,那么重启恢复后,虽然 InnoDB 发现没有 commit,但满足 prepare 和 binlog 完成,所以重启后会自动 commit。备份恢复:有 binlog。两者保持一致。

两阶段提交

两阶段提交的目的是为了 让两份日志(redo log 和 binlog)之间的逻辑一致

如果不使用两阶段提交会有什么问题?仍然用上面的 update 语句做例子。假设当前 ID=2 的行,字段 c 的值是 0,那么就会有两种形式的写入:

先写 redo log 后写 binlog

那么如果在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。因为 redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 n 的值是 1。

但是由于 binlog 还没有写就挂了,那么 binlog 里面肯定是没有这个语句的,所以如果用 binlog 恢复临时库的话,这个临时库就会少了这一次的更新,恢复出来的这一行 n 的值就是 0,与原库的值不同。

先写 binlog 后写 redo log

binlog 写完后 MySQL 进程异常重启,因为 redo log 还没有写,所以崩溃恢复以后这个事务无效,这一行的 n 的值是 0。但是 binlog 里面记录了 “把 n 从 0 改成 1”这个日志,所以用 binlog 来恢复临时库的话,就多了一个事务出来,恢复出来的这一行 n 的值就是 1,与原库的值不符。

配置

  • innodb_flush_log_at_trx_commit 参数设置为 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘,可以保证 MySQL 异常重启后数据不丢失
  • sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘,保证 MySQL 异常重启之后 binlog 不丢失

FAQ

为什么会有两份日志 redo log 和 binlog?

因为一开始 MySQL 里面并没有 InnoDB 引擎,自带的是 MyISAM,但 MyISAM 没有 crash-safe 的能力,binlog 只能用于归档存储。既然 binlog 没有 crash-safe 的能力,所以 InnoDB 使用另外一套日志系统,也就是 redo log 实现 crash-safe 能力。

redo log 和 binlog 的区别?

  • redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用
  • redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1”
  • redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是值 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志
  • redo log 记录的,即使异常重启,都会刷新到磁盘;而 binlog 记录的,则主要用于备份

binlog 是不是多余的呢?

不是。

  1. redo log 只有 InnoDB 引擎有,别的引擎没有
  2. redo log 是循环写的,不持久保存;binlog 是归档的,这个是 redo log 不具备的

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注