【MySQL】日志简述

1,668 阅读7分钟
  • 本文是笔记类型文章,只有结论没有证明,可用于复习巩固知识,不能用于新知识的学习。如有错误,恳请指正,不胜感谢。
  • 转载请于文首标明出处:【MySQL】日志简述 - 掘金 (juejin.cn)
  • 文章仍未完工,内容会逐步完善

系列文章:

binlog

binlog 称为二进制日志(Binary Log),是 MySQL 服务层的日志,用于记录执行的写操作语句,属于逻辑日志。只有事务提交后才会写 binlog,当事务提交但是数据没有落盘时如果发生宕机,则在重启之后可以通过 mysqlbinlog 和 binlog 重做事务。由于是 MySQL 服务层的日志,因此可以对所有存储引擎提供支持。

由于 MySQL 服务层不负责存储,落盘操作由存储引擎控制,因此服务层必须通过 binlog 来保证 DML 执行的持久性。

配置

  • 启用与关闭:可以通过在配置文件中配置 log-bin=[log_file_name] 或者执行 SET SQL_LOG_BIN=1 来开启 binlog。删除 log-bin 配置项或执行 SET SQL_LOG_BIN=0 可以关闭 binlog。
  • 写入方式追加写。文件大小达到配置阈值或执行 FLUSH LOGS 时会切换到新的 binlog 文件。旧文件通常需要手动或按配置策略(expire_logs_days)清除。
  • 写入时机:写入时机由 sync_binlog 控制。
    • sync_binlog=0,MySQL 不控制 binlog 的刷新,由操作系统自行在空闲时刷入磁盘。
    • sync_binlog=N,N 为正整数,表示每 N 次事务,MySQL 手动将 binlog 刷入磁盘。 因此 sync_binlog=1 可以带来最高的一致性,通常我们也是选择配置为 1。

创建

以下情况会重新创建一个 binlog 文件:

  1. MySQL 重启。
  2. 使用 flush log 命令。
  3. 文件大小超过 max_binlog_sizemax_binlog_size 默认为最大值 1G,最小值为 4k。但是当单次 DML 大小超过 1G 时,由于不会将单次操作拆分为多个文件存储,因此会出现 binlog 大小大于max_binlog_size 的情况。

内容

记录了对数据库执行的数据修改操作本身。

主要包含三种格式:

  • Statement :记录原始的 SQL 语句本身(如 INSERT INTO users (name) VALUES ('Alice');UPDATE orders SET status='shipped' WHERE id=123;)。
  • Row : 记录被修改行的具体变更内容
    • 对于 INSERT:记录新插入行的所有字段值。
    • 对于 DELETE:记录被删除行的唯一标识(通常是主键)或者完整行的旧值(取决于配置)。
    • 对于 UPDATE:记录被修改行的唯一标识(主键)以及修改后各字段的新值(ROW格式默认只记新值),有时也包含修改前的标识/值(如果配置了需要)。
  • Mixed : MySQL 动态选择使用 STATEMENT 还是 ROW 格式来记录操作。通常安全的操作用 STATEMENT,可能引起主从数据不一致的操作自动切换到 ROW。

此外,还包括事务的提交信息(BEGIN/COMMIT/ROLLBACK 等)、数据库结构变更(CREATE/DROP/ALTER TABLE 等 DDL 语句)。

用途

  • 主从复制 (Replication):从库(Slave)读取主库(Master)的 binlog 并在本地重放,以达到数据同步的目的。
  • 基于时间点或位置的恢复 (Point-in-Time Recovery, PITR):利用全量备份+binlog 重放,将数据库恢复到历史某个特定时间点或日志位置的状态。
  • 数据审计 (Auditing):分析 binlog 可以追踪所有对数据库的修改记录。

redo log

redo log 又称重做日志,是 InnoDB 存储引擎的日志,记录了在 InnoDB 存储引擎层发生的、对数据库数据页所做的物理修改,属于物理日志。redo log 仅记录事务对哪些数据页做了哪些修改,采用顺序写的方式,因此写入速度非常快,不会对性能造成太大影响。当事务执行提交之后,如果数据还未落盘数据库就宕机了,重启后 InnoDB 会自动根据 redo log 对丢失的事务进行重做。

为什么事务提交时不直接将数据落盘呢?是由于对于数据的修改很多时候都是随机的,而 InnoDB 默认页大小是 16K,因此如果一个修改涉及到多个页,此时需要进行多次随机 IO。修改完之后又需要刷入磁盘,又需要进行多次随机 IO,速度非常慢。因此对于事务的写操作通常都会将修改缓存到内存中,等待空闲时写入。

InnoDB 的 WAL(Write Ahead Log)技术的产物就是 redo log,对于写操作,永远都是日志先行,先写入 redo log 确保一致性之后,再对修改数据进行落盘

log buffer

Log Buffer 逻辑结构图 -- Zohar Yip

redo log 除了在磁盘上的文件实体之外,还在内存中有一块日志缓冲区 log buffer。如果每次进行修改都直接写磁盘的话,效率并不高,因此使用一个缓冲区先在内存中记录写操作,等到多次写操作发生,再一次写入磁盘。通过将随机写优化为顺序写,可以极大提高 redo log 的性能。redo log buffer 有三种落盘策略,由 innodb_flush_log_at_trx_commit 控制:

  • 0:按秒写,按秒刷。每秒调用 write() 写入 OS Buffer 并调用 flush() 刷入磁盘。
  • 1:实时写,实时刷。每次事务提交都调用 write() 写入 OS Buffer 并调用 flush() 刷入磁盘。
  • 2:实时写,延迟刷。每次事务提交都调用 write() 写入 OS Buffer,但每秒调用 flush() 刷入磁盘。

因此采用延迟写策略时系统运行速度最快,但是发生意外会有 1s 的数据丢失。根据使用场景选择即可。

redo log 落盘策略

writeflush 的概念是由于存在 OS Buffer,OS Buffer 是操作系统为了协调内存与磁盘的速度而提供的写缓冲区。因此我们单纯的 write() 并无法保证数据即刻落盘,因此 OS 提供 flush() 系统调用来触发立刻刷新缓冲。

内容

包含一组被称为 redo records 或 redo entries 的记录。

每条记录大致包含:

  • 被修改的表空间标识 (Tablespace ID)
  • 被修改的数据页编号 (Page Number)
  • 数据页内修改发生的起始位置 (Offset within the page)
  • 修改的具体内容(字节变化
  • (可能还有其他元数据如 LSN - Log Sequence Number)

它记录的是一种物理行为,比如“在表空间 X 的第 Y 号页面的偏移量 Z 处,将值从 A 改成了 B”,或者“向第 Y 号页面的空闲空间列表添加了一个记录”等。它不关心是哪个 SQL 语句导致了这个修改,也不直接关联到具体的行。

用途

  • 崩溃恢复 (Crash Recovery) : 这是 redo log 的核心作用。当 MySQL 异常崩溃时,重启后 InnoDB 会检查 redo log。那些在崩溃前已经写入 redo log(即事务已提交或部分提交)但尚未实际写入数据文件(*.ibd)的修改,会被重做 (redo)  一次,从而保证事务的持久性 (Durability) 。即使数据页在内存(Buffer Pool)中被修改了但没刷盘,只要对应的 redo log 落盘了,数据就不会丢失。
  • Write-Ahead Logging (WAL)  策略的体现:确保在数据页实际修改被写入磁盘之前,必须先将其物理改动的描述(redo log)持久化到磁盘。

循环写入

redo log 采用循环写入的模式,当前写入位置有一个 write_pos 指针,当前已落盘的数据日志结尾有一个 check_point 指针,也就是位于安全点之前日志可以被删除,位于安全点之后的数据不可被删除。当 write_pos 与 check_point 相遇时,redo log 空间不够,此时需要立刻将部分数据落盘,后移 check_point 以保证 redo log 的使用。

undolog

三者的比较