原创

MySQL 一致性快照备份:为什么导出期间仍要限制 DDL

mysqldump --single-transaction 能让 InnoDB 数据在一致性视图里导出,却不能把并发改表也变成一份可随意回溯的表结构快照。备份任务跑着时继续 ALTER、TRUNCATE 或重命名表,可能让导出失败,或者让得到的文件不再符合预期。

“数据还能写”与“所有数据库变更都能照常做”必须分开。前者依赖事务版本,后者还涉及表定义、元数据锁和导出程序读取每张表的时机。

先看事务快照真正保住了什么

实验固定 MySQL 8.4、InnoDB。在源实例建立独立库:

CREATE DATABASE snapshot_source;
USE snapshot_source;
CREATE TABLE wallet (
    id BIGINT NOT NULL PRIMARY KEY,
    owner_name VARCHAR(40) NOT NULL,
    balance DECIMAL(12,2) NOT NULL,
    CONSTRAINT ck_wallet_balance CHECK (balance >= 0)
) ENGINE=InnoDB;
INSERT INTO wallet VALUES (1, 'alpha', 100.00), (2, 'beta', 100.00);

打开两个固定连接。会话 A 开始快照并读取:

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
SELECT id, balance FROM snapshot_source.wallet ORDER BY id;

会话 B 完成转账:

START TRANSACTION;
UPDATE snapshot_source.wallet SET balance = balance - 10 WHERE id = 1;
UPDATE snapshot_source.wallet SET balance = balance + 10 WHERE id = 2;
COMMIT;

回到 A,重复同一个普通 SELECT,应继续看到快照内的两笔原余额;结束事务后再读,才能看到 B 提交后的状态。这个实验展示了并发 DML 与一致性读取如何共存。

SELECT id, balance FROM snapshot_source.wallet ORDER BY id;
COMMIT;
SELECT id, balance FROM snapshot_source.wallet ORDER BY id;

这里没有对表加写锁,也没有让 B 等待整份备份完成。代价是旧版本可能需要继续保留;长时间备份并非完全没有运行负担。

为什么不能把同一结论套在 DDL 上

导出程序不是瞬间把整个数据库复制到文件。它需要获取对象信息,再读取一张张表。表结构在这个过程中改变,影响的是“这段数据该按哪份定义解释”,不能仅靠旧行版本解决。

某些 DDL 会被正在使用该表的事务挡住,另一些时机则可能影响还未读取的对象。是否被挡住取决于对象、算法和锁阶段,不能用“小表上 ALTER 恰好等住了”证明所有并发 DDL 都安全。

MySQL 8.4 对 --single-transaction 的使用条件明确要求:导出期间应避免其他连接对相关表执行 ALTER、CREATE、DROP、RENAME、TRUNCATE 等操作。备份系统与数据库迁移系统必须共享这一条协调规则,而不是各自认为“自己的操作很轻”。

尤其不要将 TRUNCATE 当作大批量 DELETE。它涉及表对象与定义层面的操作,和逐行事务删除的读取边界不同。备份前只暂停应用写接口,却放任自动分区维护或归档脚本改表,也不构成稳定的导出窗口。

做一次真正能恢复的逻辑备份

准备两套独立 MySQL 8.4 实例:源端口 3307、恢复端口 3308。以下使用已配置好的实验管理账号 lab_admin,密码通过客户端提示输入。不要把命令改成连接正式库来演示。

源库停止 DDL 发布与表维护任务,普通事务写入是否继续取决于业务要求。使用与目标版本匹配的 MySQL 客户端:

mysqldump --host=127.0.0.1 --port=3307 \
  --user=lab_admin --password \
  --single-transaction --quick --set-gtid-purged=OFF \
  snapshot_source > snapshot.sql

本实验关闭 GTID_PURGED 输出,目的是向独立空库演练数据恢复;需要建立复制或做持续恢复链时,GTID 与日志位置应按对应方案保存,不能把这个选项当所有备份的通用默认。

检查退出状态和标准错误。文件已经存在、大小不为零,都不能证明导出完成;程序在中间失败仍可能留下半份文件。自动脚本应先写临时文件,仅在命令成功并完成校验后才标记为可恢复备份。

恢复实例上创建空库,再导入这个不带 --databases 的单库文件:

mysql --host=127.0.0.1 --port=3308 \
  --user=lab_admin --password \
  --execute='CREATE DATABASE snapshot_restore'

mysql --host=127.0.0.1 --port=3308 \
  --user=lab_admin --password snapshot_restore < snapshot.sql

不要在含有现有业务数据的库上试导入。备份文件可能包含 DROP TABLE 等语句,恢复演练必须有明确的空环境和独立访问范围。

校验要同时看结构和业务不变量

在恢复实例查询:

SHOW CREATE TABLE snapshot_restore.wallet;
SELECT COUNT(*) AS wallet_count,
       SUM(balance) AS total_balance
FROM snapshot_restore.wallet;
SELECT id, owner_name, balance
FROM snapshot_restore.wallet ORDER BY id;

这个样本应有两行,总余额为 200.00。余额具体是转账前还是转账后,取决于备份快照建立的时刻;不能仅凭总额正确就认定所有字段都正确,还要核对主键集合、约束定义和行内容。

真实库应选择具有业务意义的核验:关键对象数量、状态分布、关联完整性以及抽样详情。源库如果仍在写,拿“恢复库当前数量”与“源库此刻数量”硬比较,得到差异并不必然表示备份坏了;比较双方必须对应同一时间语义。

一次完整演练还应记录导出与导入版本、实例设置、开始结束时间和校验结果。没有验证恢复耗时,就无法给恢复目标一个有依据的承诺;本文的样本只验证流程,不提供任何虚构吞吐或恢复时间。

跨出 InnoDB 边界以后,要重新设计保证

--single-transaction 不会把 MyISAM、MEMORY 等非事务表自动变成一致快照。如果库里混有这些对象,需要明确冻结写入、选择其他备份方式或拆分可接受的一致性范围。只检查默认存储引擎不够,已有表可能使用别的引擎。

同样,单份 SQL 文件不是天然的时间点恢复链。误操作发生在备份之后,是否能恢复到误操作之前,还取决于连续日志、位置与保留策略。逻辑备份能成功导入,是恢复能力的一部分,不能代替整个方案。

备份窗口最该协调的不是一句“别动数据库”,而是哪些写入可以继续、哪些 DDL 必须暂停、谁来判断任务完成,以及恢复验证何时算通过。这些约定写清楚,--single-transaction 才能提供它实际承诺的那部分一致性。

正文到此结束
评论插件初始化中...
Loading...