`InnoDB`的`崩溃`恢复`（`Crash Recovery`）：`redo log`在`实例`启动时的`重放`流程。

admin
Sat, 06 Sep 2025 11:39:03 +0000
Mysql, crash, innodb, recovery

好的，我们开始。 InnoDB 崩溃恢复：Redo Log 重放流程详解 各位学员，大家好！今天我们来深入探讨InnoDB存储引擎在发生崩溃后，如何利用Redo Log进行数据恢复的关键流程。Redo Log是InnoDB实现ACID特性中持久性（Durability）的重要保障。 理解其重放机制对于数据库管理员、开发者以及DBA来说至关重要。 1. Redo Log 的基本概念 Redo Log，即重做日志，记录了InnoDB存储引擎中所有对页（Page）的修改操作。当数据库发生崩溃时，Redo Log可以被用来重新执行这些修改，从而将数据库恢复到崩溃前的状态。 物理日志: Redo Log记录的是物理层面的修改，即具体哪个页的哪个位置发生了什么变化。 循环写入: Redo Log文件通常是循环写入的，由多个物理文件组成（如ib_logfile0, ib_logfile1等）。 LSN (Log Sequence Number): 每个Redo Log条目都有一个唯一的LSN，用于标识Redo Log的顺序。LSN是一个递增的数值，可以用来判断Redo Log的先后顺序。 Check …

继续阅读“`InnoDB`的`崩溃`恢复`（`Crash Recovery`）：`redo log`在`实例`启动时的`重放`流程。”

The post `InnoDB`的`崩溃`恢复`（`Crash Recovery`）：`redo log`在`实例`启动时的`重放`流程。 first appeared on 智猿学院-前后端，数据库，人工智能，云计算等领域前沿技术讲座.

好的，我们开始。

InnoDB 崩溃恢复：Redo Log 重放流程详解

各位学员，大家好！今天我们来深入探讨InnoDB存储引擎在发生崩溃后，如何利用Redo Log进行数据恢复的关键流程。Redo Log是InnoDB实现ACID特性中持久性（Durability）的重要保障。 理解其重放机制对于数据库管理员、开发者以及DBA来说至关重要。

1. Redo Log 的基本概念

Redo Log，即重做日志，记录了InnoDB存储引擎中所有对页（Page）的修改操作。当数据库发生崩溃时，Redo Log可以被用来重新执行这些修改，从而将数据库恢复到崩溃前的状态。

物理日志: Redo Log记录的是物理层面的修改，即具体哪个页的哪个位置发生了什么变化。

循环写入: Redo Log文件通常是循环写入的，由多个物理文件组成（如ib_logfile0, ib_logfile1等）。

LSN (Log Sequence Number): 每个Redo Log条目都有一个唯一的LSN，用于标识Redo Log的顺序。LSN是一个递增的数值，可以用来判断Redo Log的先后顺序。

Checkpoint: Checkpoint指的是一个已经将所有修改的数据页刷新到磁盘上的时间点。Checkpoint LSN标识了Redo Log中可以被丢弃的最早的日志位置。

2. Redo Log 的结构

Redo Log的结构可以简化理解为一系列的Redo Log条目，每个条目记录了一个或多个页的修改。一个简化的Redo Log条目结构可能如下：

字段 | 描述
LSN | 日志序列号，唯一标识Redo Log条目
Type | 日志类型，指示Redo Log记录的操作类型（例如，插入、更新、删除等）
Table ID | 表ID，标识修改操作所属的表
Page Number | 页号，标识被修改的页
Offset | 偏移量，标识修改操作在页内的起始位置
Data | 修改后的数据
Checksum | 校验和，用于验证Redo Log条目的完整性

3. Checkpoint 机制

Checkpoint机制是Redo Log管理的关键。它的主要作用是将脏页（已修改但尚未刷新到磁盘的页）刷新到磁盘。 Checkpoint 会定期进行，或者在Redo Log空间不足时被触发。

作用:

缩短恢复时间：在崩溃恢复时，只需要重放Checkpoint之后的Redo Log。

回收Redo Log空间：Checkpoint之前的Redo Log可以被覆盖。

类型:

模糊检查点（Fuzzy Checkpoint）： InnoDB使用模糊检查点，允许在刷新脏页的同时，继续接受新的写入操作。 这提高了并发性，但增加了恢复的复杂性。

4. 崩溃恢复流程

当InnoDB实例启动时，如果检测到上次运行过程中发生了崩溃，它会启动崩溃恢复流程。该流程主要包含以下步骤：

1. 确定恢复起点: InnoDB首先需要确定从哪个LSN开始重放Redo Log。通常这个起点是最近一次Checkpoint的LSN。InnoDB会读取Redo Log文件的头部信息，从中获取Checkpoint LSN。

2. 扫描Redo Log: InnoDB从恢复起点开始，顺序扫描Redo Log文件。

3. 构建Undo Log (可选): 对于某些类型的Redo Log条目（例如，插入操作），InnoDB可能需要生成相应的Undo Log条目。Undo Log用于在必要时回滚未完成的事务。

4. 重放Redo Log: InnoDB根据Redo Log条目中记录的信息，将修改应用到相应的页。 这包括读取页，应用修改，然后将修改后的页标记为脏页。

5. 应用Checkpoint: InnoDB将最新的Checkpoint LSN更新到Redo Log文件的头部。

6. 完成恢复: InnoDB完成Redo Log的重放，并将所有脏页刷新到磁盘。

5. Redo Log 重放的详细步骤

现在，我们更详细地探讨Redo Log重放的步骤，并结合代码片段进行说明。

5.1 确定恢复起点

InnoDB会读取Redo Log文件的头部信息，从中获取Checkpoint LSN。 这通常涉及到读取文件的前几个字节，并解析出LSN的值。

// 假设 redo_log_file 是一个文件句柄
// 假设 Checkpoint LSN 存储在文件头部的某个固定偏移量处

off_t checkpoint_lsn_offset = 16; // 假设偏移量为16字节
lsn_t checkpoint_lsn;

// 读取Checkpoint LSN
pread(redo_log_file, &checkpoint_lsn, sizeof(lsn_t), checkpoint_lsn_offset);

std::cout << "Checkpoint LSN: " << checkpoint_lsn << std::endl;

5.2 扫描Redo Log

InnoDB从Checkpoint LSN开始，顺序读取Redo Log文件。 这涉及到读取Redo Log条目的头部信息，确定条目的类型和长度，然后读取条目的数据。

// 假设 current_lsn 是当前读取到的LSN
lsn_t current_lsn = checkpoint_lsn;

while (true) {
  // 读取Redo Log条目的头部
  redo_log_header_t header;
  ssize_t bytes_read = pread(redo_log_file, &header, sizeof(redo_log_header_t), current_lsn);

  if (bytes_read != sizeof(redo_log_header_t)) {
    // 文件结束或读取错误
    break;
  }

  // 校验Redo Log条目的校验和
  if (!verify_checksum(&header)) {
    // 校验和错误，日志损坏
    std::cerr << "Checksum error at LSN: " << current_lsn << std::endl;
    break;
  }

  // 根据日志类型，读取Redo Log条目的数据
  switch (header.type) {
    case REDO_LOG_INSERT: {
      // 处理插入操作
      redo_log_insert_t insert_data;
      pread(redo_log_file, &insert_data, sizeof(redo_log_insert_t), current_lsn + sizeof(redo_log_header_t));

      // ... 应用插入操作 ...
      apply_insert_redo_log(insert_data);

      break;
    }
    case REDO_LOG_UPDATE: {
      // 处理更新操作
      redo_log_update_t update_data;
      pread(redo_log_file, &update_data, sizeof(redo_log_update_t), current_lsn + sizeof(redo_log_header_t));

      // ... 应用更新操作 ...
      apply_update_redo_log(update_data);
      break;
    }
    // ... 其他日志类型 ...
  }

  // 更新当前LSN
  current_lsn += header.length;
}

5.3 应用 Redo Log

应用Redo Log意味着将Redo Log条目中记录的修改应用到相应的页。 这涉及到读取页，应用修改，然后将修改后的页标记为脏页。

// 假设 page_cache 是一个页缓存，用于存储从磁盘读取的页
// 假设 disk_io 是一个磁盘IO接口，用于读取和写入页

// 应用插入操作的示例
void apply_insert_redo_log(const redo_log_insert_t& insert_data) {
  page_id_t page_id = insert_data.page_id;
  off_t offset = insert_data.offset;
  size_t data_length = insert_data.data_length;
  const char* data = insert_data.data;

  // 从页缓存中获取页
  page_t* page = page_cache->get_page(page_id);

  if (page == nullptr) {
    // 如果页不在缓存中，则从磁盘读取
    page = disk_io->read_page(page_id);
    if (page == nullptr) {
      std::cerr << "Failed to read page: " << page_id << std::endl;
      return;
    }
    page_cache->put_page(page_id, page); // 将读取的页放入缓存
  }

  // 应用修改
  memcpy(page->data + offset, data, data_length);

  // 将页标记为脏页
  page->is_dirty = true;
}

// 应用更新操作的示例
void apply_update_redo_log(const redo_log_update_t& update_data) {
  page_id_t page_id = update_data.page_id;
  off_t offset = update_data.offset;
  size_t data_length = update_data.data_length;
  const char* data = update_data.data;

  // 从页缓存中获取页
  page_t* page = page_cache->get_page(page_id);

  if (page == nullptr) {
    // 如果页不在缓存中，则从磁盘读取
    page = disk_io->read_page(page_id);
    if (page == nullptr) {
      std::cerr << "Failed to read page: " << page_id << std::endl;
      return;
    }
    page_cache->put_page(page_id, page); // 将读取的页放入缓存
  }

  // 应用修改
  memcpy(page->data + offset, data, data_length);

  // 将页标记为脏页
  page->is_dirty = true;
}

5.4 脏页刷新

在Redo Log重放完成后，InnoDB需要将所有脏页刷新到磁盘，以确保数据的持久性。

// 假设 page_cache 是一个页缓存
// 假设 disk_io 是一个磁盘IO接口

void flush_dirty_pages() {
  // 遍历页缓存中的所有页
  for (auto& entry : page_cache->pages) {
    page_t* page = entry.second;
    if (page->is_dirty) {
      // 将脏页刷新到磁盘
      disk_io->write_page(page);
      page->is_dirty = false;
    }
  }
}

6. ARIES 恢复算法

InnoDB的崩溃恢复机制基于ARIES（Algorithm for Recovery and Isolation Exploiting Semantics）恢复算法。 ARIES算法是一种广泛应用于数据库系统的恢复算法，它具有以下特点：

Write-Ahead Logging (WAL): 在修改任何数据页之前，必须先将相应的Redo Log条目写入磁盘。

Repeat History: 在恢复过程中，InnoDB会尽可能地重做所有已完成的事务，即使这些事务在崩溃之前已经提交。

Undo Incomplete Transactions: 对于在崩溃时尚未完成的事务，InnoDB会使用Undo Log进行回滚。

7. 示例：模拟崩溃恢复

为了更好地理解崩溃恢复流程，我们可以模拟一个简单的崩溃恢复场景。

1. 启动数据库: 启动InnoDB实例，并执行一些插入和更新操作。

2. 创建Checkpoint: 手动执行Checkpoint操作，将脏页刷新到磁盘。

3. 模拟崩溃: 强制关闭数据库实例，模拟崩溃发生。

4. 重启数据库: 重新启动数据库实例。InnoDB会自动启动崩溃恢复流程。

5. 验证数据: 验证数据库中的数据是否与崩溃前一致。

8. 优化崩溃恢复

崩溃恢复是一个耗时的过程，特别是当Redo Log文件很大时。以下是一些优化崩溃恢复的方法：

增加Redo Log文件的大小: 增加Redo Log文件的大小可以减少Checkpoint的频率，从而减少磁盘IO。

使用SSD: 使用SSD可以显著提高磁盘IO性能，从而加快崩溃恢复速度。

调整innodb_flush_log_at_trx_commit参数: innodb_flush_log_at_trx_commit参数控制Redo Log刷新的频率。将其设置为0或2可以提高性能，但会降低数据的安全性。

监控redo log的使用情况: 通过监控Innodb_os_log_written, Innodb_os_log_fsyncs, Innodb_os_log_pending_fsyncs等状态变量，可以了解Redo Log的使用情况，并根据需要进行调整。

9. 关键配置参数

以下是一些与Redo Log相关的关键配置参数：

参数 | 描述
innodb_log_file_size | 每个Redo Log文件的大小。 较大的文件减少了检查点的频率，但延长了恢复时间。
innodb_log_files_in_group | Redo Log文件的数量。 默认值为2。
innodb_flush_log_at_trx_commit | 控制Redo Log的刷新策略。
innodb_flush_method | 控制InnoDB如何刷新数据和日志文件。 不同的方法对性能和可靠性有不同的影响。
innodb_checksums | 控制是否启用校验和。启用校验和可以提高数据的可靠性，但会降低性能。

10. 深入理解，灵活应用

今天我们深入探讨了InnoDB的Redo Log重放流程，涵盖了Redo Log的基本概念、结构、Checkpoint机制、崩溃恢复流程、ARIES恢复算法、优化方法以及关键配置参数。理解这些内容，可以帮助我们更好地管理和维护InnoDB数据库，并在发生崩溃时快速恢复数据。希望这些知识能对各位有所帮助。

关键点回顾

Redo Log 是InnoDB持久性的关键，记录了所有数据页的修改。

Checkpoint 机制缩短恢复时间，并回收Redo Log空间。

崩溃恢复流程涉及确定恢复起点、扫描Redo Log、应用Redo Log和刷新脏页。

ARIES 算法是InnoDB崩溃恢复的基础。