原创声明

本文节选自:


概述

Innodb 事务日志包括:redo log 和 undo log。

undo log 不是 redo log 的逆向过程,它们都是用于恢复的日志:


redo log 的基本概念

redo log 包含两部分:

innodb 通过 force log at commit 机制实现事务的持久性。即在事务提交时,必须先将所有事务日志写到磁盘上的 redo log file 和 undo log file。

为了确保每次都能将事务日志写到文件中,在每次将 log buffer 中的日志写到 log file 的过程中,都会调用操作系统的 fsync()。

将 redo log buffer 中的日志写到磁盘的 redo log file 的过程如下:

redo-log-1.png


日志刷盘的规则

log buffer 中未被刷到磁盘的日志叫脏日志(dirty log)。

将脏日志刷到磁盘有以下几种规则:


LSN

LSN 是日志的逻辑序列号(Log Sequence Number),在 innodb 中,LSN 占 8 个字节,LSN 的值会随着日志的写入而增大。

根据 LSN 可以获取到以下信息:

LSN 不仅存在于 redo log 中,还存在于数据页中,数据页头部的 fil_page_lsn 用于记录当前页的最终 LSN。如果数据页中的 LSN 比redo log 中的 LSN 小,则表示发生了数据丢失,此时可以通过 redo log 来恢复。


innodb 的恢复行为

在启动 innodb 时,不管上次是正常关闭,还是异常关闭,总是会进行恢复操作

checkpoint 表示已经完整刷到磁盘上的 data page 的 LSN。因此恢复时,仅需要恢复从 checkpoint 开始的日志。例如,当数据库在 checkpoint 的 LSN 为 10000 时宕机,且事务是已提交的状态,那么启动时会检查磁盘中数据页的 LSN,如果数据页的 LSN 小于日志中的 LSN,则从检查点开始恢复。


undo log

undo log 用于:

innodb 在修改数据时,不仅记录 redo log,还记录相对应的 undo log,如果因为某些原因导致事务失败或回滚,可以借助 undo log 进行恢复。

undo log 是逻辑日志,可以认为当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,反之亦然,当 update 一条记录时,它会记录一条相反的 update 记录。

当执行 rollback 时,就可以从 undo log 中读取相应的内容并进行回滚。

undo log 也可以用于实现 MVCC。

innodb 使用段的方式管理 undo log。在 innodb 中共有 128 个回滚段(rollback segment),每个回滚段(rollbock segment slot)由 1024 个 undo segment 组成,每个事务占用 1 个 undo segment。

回滚段采用轮训调度的方式来分配使用,如果回滚段正在 truncate 则不分配。

另外,undo log 也会产生 redo log,因为 undo log 也要实现持久性保护。


2PC 和 group commit

为了确保 redo log 与 binlog 一致,MySQL 采用 2PC 来保证事务的完整性:

  1. 调用 binlog 和 innodb 的 prepare() 方法

    • binlog 的 prepare() 方法什么也不做
    • innodb 的 preprare() 方法将事务状态设置为 TRX_PREPARED,并将 redo log 刷盘
  2. 如果事务涉及到的所有存储引擎的 prepare() 方法都执行成功,则将 SQL 记录到 binlog,否则回滚

  3. 调用 commit() 方法完成事务的提交

    • binlog 的 commit() 方法什么也不会做,因为第二步已经将 binlog 刷盘
    • innodb 的 commit() 方法清理 undo 信息、刷 redo log、将事务状态设置为 TRX_NOT_STARTED

innodb 在恢复时,会根据事务的状态,进行不同的处理:

MySQL 打开 binlog 时,会检查 binlog 的状态,如果 binlog 没有正常关闭,则进行恢复操作,基本流程如下:

组提交的原理是:对于一组提交操作,只执行一次某些低级 I/O 操作,而不是每次提交都 flush 和 sync。

MySQL 5.6 将事务的提交分为三个阶段:

mysql-group-commit.png

在 MySQL 中每个阶段都有一个队列,每个队列都有一把锁保护,第一个进入队列的事务会成为 leader,leader 领导所在队列的所有事务,全权负责整队的操作,完成后通知队内其它事务操作结束。


See Also