bksczm头像
MySQL进阶篇之日志(上半篇)封面图

MySQL进阶篇之日志(上半篇)

 bksczm

📚 本文收录于「bksczm」的系列专栏

LinuxLinuxC/C++C/C++
MySQLMySQL面试题面试题
RedisRedis

一、MySQL 日志体系总览

1.1 为什么需要日志

日志用于记录服务器正在发生的活动,是数据安全、崩溃恢复、主从复制、故障排查的基础。MySQL 中常见的日志有七类:

日志作用所属层
redo log事务提交后崩溃恢复,保证持久性(DML 物理修改)InnoDB 引擎层
undo log事务回滚 + MVCC 多版本并发控制(旧版本链)InnoDB 引擎层
error log记录 mysqld 启动、运行、停止时的错误与告警Server 层
general query log记录所有客户端连接和收到的语句(调试用,默认关闭)Server 层
slow query log记录执行时间超过 long_query_time 的慢查询Server 层
binary log(binlog)记录所有对数据的更改,用于复制和恢复Server 层
relay log(中继日志)从库接收主库 binlog 后落地的日志,供 SQL 线程重放Server 层(从库)

1.2 必须先建立的分层概念(重点)

这是理解 redo log 和 binlog 的前提:

  • redo log / undo log 是 InnoDB 存储引擎层的日志,只有使用 InnoDB 才有;MyISAM 没有崩溃恢复能力正是因为没有 redo log。
  • binlog 是 MySQL Server 层的日志,与存储引擎无关,任何引擎对数据的更改都会记录 binlog。

一条更新语句在 InnoDB 中会同时产生 redo log(引擎层)和 binlog(Server 层),二者通过两阶段提交协调(见第七章)。


二、binlog 是什么

2.1 概念与特点

二进制日志(binary log,binlog)以"事件(event)"的形式,记录所有导致(或可能导致)数据变化的操作:

  • 记录:DDL(CREATE/ALTER/DROP)、DML(INSERT/UPDATE/DELETE);
  • 不记录:DQL(SELECT/SHOW 等纯查询,因为不改变数据)。

binlog 的三个本质特征:

  1. 逻辑日志:记录的是"执行了什么语句"或"哪些行从什么值变成什么值",而不是数据页的物理字节变化(对比 redo log 是物理日志);
  2. 追加写:日志文件按编号递增(binlog.000001、000002……),写满一个切下一个,不会循环覆盖旧内容(对比 redo log 是固定大小循环写);
  3. 二进制格式:有自己严格定义的二进制事件结构,不是纯文本,需要用 mysqlbinlog 工具解码查看。(并非没有编码的裸数据)

2.2 binlog 与 redo log 的区别(高频面试题)

对比维度redo logbinlog
所属层InnoDB 引擎层MySQL Server 层(所有引擎通用)
日志类型物理日志:某数据页某偏移做了什么修改逻辑日志:语句或行变更逻辑
写入方式循环写,固定文件大小,写满回头覆盖追加写,文件不断切换,不覆盖
主要用途崩溃恢复(crash recovery),保证持久性主从复制、数据恢复(point-in-time recovery)
记录内容事务执行过程中持续产生事务提交时一次性写入

打个比方:redo log 像一块循环使用的"应急小黑板",只关心数据库崩溃后能把已提交事务恢复回来;binlog 像一本不断追加的"操作流水账",可以随时翻出来重放或给从库照抄。

2.3 binlog 的两大用途

  1. 主从复制:主库把 binlog 传给从库,从库重放得到相同数据(见第九章);
  2. 数据恢复:备份恢复 + 重放 binlog,可以把数据库恢复到任意时间点/任意位置。

三、binlog 的开启与相关变量

3.1 查看相关变量

SHOW VARIABLES LIKE '%log_bin%';
变量含义
log_bin是否开启 binlog(MySQL 8.0 默认 ON;5.7 默认 OFF,需手动开启)
log_bin_basenamebinlog 文件的存储路径和文件名前缀,如 /var/lib/mysql/binlog
log_bin_indexbinlog 索引文件路径(binlog.index),维护当前有哪些 binlog 文件
log_bin_trust_function_creators见下方说明
sql_log_bin当前会话是否写 binlog,默认 ON
log_bin_use_v1_row_events已弃用

关于 log_bin_trust_function_creators :开启 binlog 后,MySQL 默认会限制创建存储函数/触发器,要求函数必须声明为 DETERMINISTIC 或 READS SQL DATA,因为不确定函数(含 RAND()、NOW() 等)在主从上重放可能产生不一致结果。把它设为 ON 表示"信任创建者",跳过这一限制检查。

3.2 log_bin 与 sql_log_bin 的辨析

变量作用范围修改方式
log_bin全局,决定整个实例是否开启 binlog只读变量,只能在配置文件或启动参数 --log-bin 中设置,不能运行时 SET 修改
sql_log_bin会话级,决定当前会话的操作是否写入 binlog可在会话中动态 SET sql_log_bin = OFF;(需较高权限,不能在事务中设置)

典型用途:数据量大的一次性批量导入,临时 SET sql_log_bin=OFF 可以避免产生海量 binlog(但要自行承担该操作不参与复制/无法通过 binlog 恢复的后果)。

3.3 在配置文件中开启

Ubuntu 22.04 下入口配置是 /etc/mysql/my.cnf,它通常再引入 /etc/mysql/mysql.conf.d/mysqld.cnf,实际改动一般在后者的 [mysqld] 组下:

[mysqld]
server-id=1
log-bin=mysql-bin
binlog_format=ROW
expire_logs_days=30

主从复制场景中 server-id 必须配置且全集群唯一。修改后重启 mysqld 生效。


四、binlog 的文件组织与切换

4.1 磁盘文件

按 log_bin_basename + 递增编号生成:

/var/lib/mysql/binlog.000001
/var/lib/mysql/binlog.000002
...
/var/lib/mysql/binlog.index      # 文本索引文件,记录所有 binlog 文件名

查看已有 binlog 文件及大小:

SHOW BINARY LOGS;

单个文件的容量上限:

SELECT @@max_binlog_size;   -- 默认 1073741824 字节 = 1GB

4.2 生成新 binlog 文件(日志切换)的时机

  1. MySQL 服务器重启;
  2. 手动刷新:
    FLUSH LOGS;            -- 刷新所有日志
    FLUSH BINARY LOGS;     -- 只切换 binlog:当前文件封盘,新建下一个编号文件
    
  3. 当前文件大小达到 max_binlog_size。

4.3 大事务不会被拆到两个文件

如果一个事务产生的日志让文件超过了 max_binlog_size,整个事务仍然完整写在当前文件中,不会跨文件拆分——因为事务必须保证完整性,恢复/复制时一个事务要能完整重放。因此实际文件可能略微超过 1GB,超大事务甚至会让单个 binlog 文件远大于上限。


五、binlog 的删除策略

不要直接在 shell 里 rm binlog 文件,否则会造成 binlog.index 索引与磁盘文件不一致,引发报错。应使用 MySQL 提供的命令。

5.1 过期自动删除

由 binlog_expire_logs_seconds 控制保留时长(MySQL 8.0 变量,单位秒):

SELECT @@global.binlog_expire_logs_seconds;  -- 默认 2592000 秒 = 30 天

5.7 版本使用 expire_logs_days(单位天)。超过保留期的 binlog 会被自动清理。

5.2 手动删除

-- 1)删除全部 binlog,编号重新从 .000001 开始(危险,主从环境慎用)
RESET MASTER;

-- 2)删除指定文件之前的所有文件:删除 binlog.000001 ~ 000004,保留 000005 及之后
PURGE BINARY LOGS TO 'binlog.000005';

-- 3)删除指定时间之前的所有文件
PURGE BINARY LOGS BEFORE '2026-10-12 21:55:59';

六、binlog 的三种记录格式

由 binlog_format 控制:

SELECT @@global.binlog_format;   -- MySQL 5.7+ 默认 ROW

6.1 STATEMENT(语句格式)

原样记录每一条 SQL 语句。

  • 优点:日志紧凑、体积小,可读;
  • 缺点:不确定函数会导致主从不一致。例如记录的是 INSERT ... VALUES(NOW()),从库在另一个时间重放时 NOW() 取到的是从库当前时间,而不是主库执行时的那个时间;UUID()、RAND()、USER() 同理。

6.2 ROW(行格式,默认推荐)

记录表中每一行数据的变更(变更前的旧镜像 before-image 和变更后的新镜像 after-image),不再依赖 SQL 上下文。

  • 优点:记录的是确定的数据结果,任何函数、复杂语句都能正确复制,主从一致性最有保障;
  • 缺点:一条 UPDATE ... WHERE 改了 100 万行,就要记录 100 万行的变更,日志体积大。

6.3 MIXED(混合格式)

MySQL 自动判断:普通确定性语句用 STATEMENT 记录,遇到不确定函数/语句自动切换为 ROW,兼顾体积和安全。

6.4 三种格式对比

格式记录内容日志体积一致性适用
STATEMENT原始 SQL小不确定函数有风险简单确定语句
ROW每行变更镜像大最可靠默认、生产推荐
MIXED自动选择居中较可靠折中场景

七、binlog 刷盘策略与两阶段提交

7.1 数据落盘的三级路径

很多人把 "写 binlog" 理解成一步到位写进磁盘文件,实际上数据从产生到真正持久化,要经过两级缓冲、两次系统调用,跨越三个位置:

        事务执行中不断产生 binlog 事件
                    │
                    ▼
┌───────────────────────────────────┐
│ ① 线程私有 binlog cache            │  MySQL 进程的「用户态内存」
│   (由 binlog_cache_size 控制)     │  每个会话线程一个,暂存本事务的事件
└─────────────────┬─────────────────┘
                  │ 事务提交时调用 write():数据从用户态拷贝到内核态
                  ▼
┌───────────────────────────────────┐
│ ② OS Page Cache(页缓存)          │  操作系统「内核态内存」
│   binlog 文件对应的脏页            │  此时逻辑上文件已有数据,但仍在内存
└─────────────────┬─────────────────┘
                  │ 调用 fsync()/fdatasync():强制把脏页刷到物理设备
                  ▼
┌───────────────────────────────────┐
│ ③ 物理磁盘                         │  真正持久化,断电不丢
│   binlog.0000xx 的磁盘扇区          │
└───────────────────────────────────┘

三级位置分别是:

  1. 线程私有 binlog cache:事务执行期间,产生的 binlog 事件先暂存在会话线程(服务器)私有的内存缓存中(大小由 binlog_cache_size 控制),事务提交时整段写出,保证一个事务的 binlog 连续完整;
  2. OS Page Cache(页缓存):调用 write() 后,数据从 MySQL 用户态内存拷贝到操作系统内核的页缓存;
  3. 物理磁盘:调用 fsync() 后,页缓存中的脏页才被强制写入磁盘扇区。

7.2 write () 与 fsync () 的区别(核心)

这是理解一切 "刷盘参数" 的关键,二者绝不能混为一谈:

系统调用数据移动返回后断电是否丢数据
write()① 用户态内存 → ② 内核 Page Cache会丢,数据还停留在内存
fsync()② Page Cache → ③ 物理磁盘不丢(磁盘自带写缓存且无掉电保护 BBU 的硬件问题除外)

执行完 write() 后,程序 "读文件" 已经能读到这些数据(因为读文件同样经过 Page Cache),文件逻辑大小也增长了,但物理字节可能还没写进磁盘;只有 fsync() 等磁盘确认写完才返回,才算真正落盘。

7.3 sync_binlog 的三个取值

sync_binlog 控制的是什么时候做 fsync(而不是 write):

取值提交时的动作崩溃语义
0只 write() 到 Page Cache,不主动 fsync,刷盘时机交给 OS(Linux 约 30 秒脏页刷盘周期)mysqld 进程崩溃不丢(Page Cache 由内核管理,进程死了内核还在);OS 崩溃 / 断电会丢未落盘部分
1每次事务提交都 write() + fsync(),一步到第③级最安全,fsync 成功后断电也不丢;性能损耗最大
N(N>1)每提交 N 个事务才 fsync() 一次,中间事务只停在第②级折中,OS 崩溃 / 断电时最多丢失约 N 个事务的 binlog

⚠️ 注意区分两种 "崩溃":

mysqld 进程崩溃但操作系统正常时,Page Cache 中的数据仍然存在、不会丢;

只有操作系统崩溃或机器断电导致内核内存失效时,未 fsync 的数据才会丢。笼统地说 "MySQL 崩溃 binlog 全丢" 是不准确的。

redo log 走的是完全相同的两级模型,可对照记忆:

redo log buffer(InnoDB 用户态内存)──write()──▶ OS Page Cache ──fsync()──▶ 物理磁盘

innodb_flush_log_at_trx_commit:=0 提交时不主动刷、后台每秒刷(进程崩溃也可能丢约 1 秒);=1 每次提交 write+fsync(最严格);=2 每次提交只 write 到 Page Cache、每秒 fsync(进程崩溃不丢,断电 / OS 崩溃丢约 1 秒)。

7.4 "双 1" 配置

生产环境最严格的持久化组合,保证 redo log 和 binlog 在每次事务提交时都真正落盘:

[mysqld]
innodb_flush_log_at_trx_commit = 1   # redo log 每次提交 fsync
sync_binlog = 1                      # binlog 每次提交 fsync

7.5 redo log 与 binlog 的两阶段提交(2PC)

一条更新语句在 InnoDB 中会同时产生 redo log(引擎层)和 binlog(Server 层)。为了让两份日志在崩溃时仍保持一致,InnoDB 采用内部两阶段提交:

1. prepare 阶段:写 redo log,将事务标记为 PREPARE 状态
2. 写 binlog:把整段事务事件 write 到 binlog 并 fsync 落盘
3. commit 阶段:在 redo log 中写入 COMMIT 标记,事务真正提交

崩溃恢复时按以下规则判定:

  • redo log 中已是 COMMIT 状态 → 提交该事务;
  • redo log 中只有 PREPARE:
    • 对应事务的 binlog 已完整 fsync 落盘 → 仍然提交(保证从库也能收到该事务);
    • binlog 不完整或不存在 → 回滚该事务。

这样就保证了 "binlog 里有的事务,主库一定提交;主库提交的事务,binlog 一定完整",主库与从库不会因崩溃而不一致


八、mysqlbinlog 工具

binlog 是二进制文件,mysqlbinlog 是官方解码工具(随 MySQL 客户端安装)。

8.1 事件内容解读

mysqlbinlog --no-defaults binlog.000010

典型输出:

# at 3470
#241021 16:38:31 server id 1  end_log_pos 3547 CRC32 0x3bda21ce Query  thread_id=8 exec_time=0 error_code=0
SET TIMESTAMP=1729499911/*!*/;
BEGIN
字段含义
# at 3470该事件在文件中的起始字节偏移量(position)
241021 16:38:31事件发生时间戳
server id 1产生该事件的服务器 ID(主从区分用)
end_log_pos 3547下一个事件的起始偏移(当前事件结束位置 +1)
Query事件类型(如 Query/Write_rows/Xid 等)
thread_id=8执行该事件的客户端线程 ID
exec_time=0主库上事件执行耗时(0 表示极快)
error_code=0错误码,0 表示无错误

8.2 常用选项

选项作用
--database=db名只查看指定数据库的事件
--start-datetime / --stop-datetime按时间范围读取
--start-position=N / --stop-position=N按字节位置范围读取(定点恢复用)
--base64-output=DECODE-ROWS把 ROW 行事件解码(配合 -v 使用)
-v / --verbose把行事件重建为注释形式的 SQL(### INSERT ...)
-r 文件名输出到指定文件
-R / --read-from-remote-server从远程服务器读取 binlog 而非本地文件
--server-id=N只显示指定 server-id 产生的事件
--skip-gtids输出时不包含 GTID 信息

8.3 查看 ROW 格式内容(纠正"加密"说法)

mysqlbinlog --no-defaults --database=testdb \
  --base64-output=DECODE-ROWS -v binlog.000001 | less

解码后能看到形如:

### INSERT INTO `testdb`.`student`
### SET
###   @1=1
###   @2='张三'
###   @3=18

8.4 用 binlog 做定点数据恢复(实操)

先用备份恢复到某个基线,再重放 binlog 中指定区间,跳过误操作区间:

# 按位置恢复(恢复 position 1000 ~ 5000 之间的操作)
mysqlbinlog --no-defaults \
  --start-position=1000 --stop-position=5000 \
  binlog.000010 | mysql -u root -p

# 按时间恢复
mysqlbinlog --no-defaults \
  --start-datetime="2026-10-12 20:00:00" \
  --stop-datetime="2026-10-12 21:00:00" \
  binlog.000010 | mysql -u root -p

这就是 PITR(Point-In-Time Recovery,时间点恢复)。


九、主从复制与 relay log

binlog 是主从复制的核心,整体流程涉及三个线程:

主库(Master)                          从库(Slave)
┌──────────────┐    ① binlog 事件      ┌────────────────────┐
│ Binlog Dump  │ ───────────────────▶ │ ② IO 线程          │
│ 线程         │   (TCP 网络传输)     │    写入 relay log  │
└──────────────┘                       └─────────┬──────────┘
                                                 │ ③ SQL 线程读取并重放
                                                 ▼
                                          从库数据与主库一致
  1. 从库 IO 线程连接主库,请求拉取 binlog;
  2. 主库 Binlog Dump 线程读取 binlog 事件发给从库;
  3. 从库 IO 线程把收到的事件写入本地的 relay log(中继日志);
  4. 从库 SQL 线程读取 relay log 并重放,使数据与主库一致。

relay log 格式与 binlog 几乎相同,区别在于它是从库"中转暂存"主库变更的日志。MySQL 5.7+ 支持基于组提交的多线程复制(MTS)提升重放速度;开启 GTID 后可免去手工指定 binlog 文件和位置。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/bksczm/article/details/167702749

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--