gt-checksum v4.0.0 新功能解读系列文章(3):反向回滚 SQL——修复可审计、可回退

在数据修复场景中,"修复容易,回滚困难"一直是个痛点。执行完修复 SQL 后发现结果不符合预期,却没有现成的回退手段。

gt-checksum v4.0.0 新增反向回滚 SQL 生成能力,让每一次修复都有"后悔药"。


一、功能简介

genRollSQL 是 gt-checksum v4.0.0 新增的核心参数,配合 maxRollRowNumrollFileDir 一起使用,用于在生成修复 SQL 的同时自动生成反向回滚 SQL

参数说明

参数 默认值 可选值 说明
genRollSQL OFF ON / OFF / 自定义表名 控制是否生成回滚 SQL
maxRollRowNum 10000 正整数 单表待修复行数超过该阈值时不生成回滚 SQL
rollFileDir rollsql 目录路径 回滚 SQL 文件存储目录

三种 genRollSQL 模式的区别:

模式 行为
OFF 不生成回滚 SQL(默认行为)
ON 对所有校验的表生成回滚 SQL
自定义表名 仅对指定的表生成回滚 SQL,支持逗号分隔多个表名,支持 % 通配符

使用方式非常简单,在配置文件 gc.conf 中添加几行:

genRollSQL=ON
rollFileDir=rollsql
maxRollRowNum=10000

二、功能作用及使用场景深入解读

2.1 为什么需要回滚 SQL?

在生产环境中执行数据修复,面临的风险远比测试环境复杂:

场景一:审计与合规

在金融、医疗等对数据变更审计要求严格的行业,不仅需要知道"改了什么",还需要知道"怎么改回去"。回滚 SQL 是修复操作可审计性的重要组成部分。

场景二:修复逻辑误判

在复杂的类型映射、字符集转换场景下,校验工具判定的"差异"可能只是格式差异而非真实数据差异(例如 DECIMAL 精度差异、utf8mb4_general_ci vs utf8mb4_0900_ai_ci 的 collation 差异)。修复后业务验证不通过,需要回退。

场景三:修复导致业务中断

修复 SQL 中的 DELETEUPDATE 可能与正在运行的业务事务产生锁冲突,或者修复了业务正在使用的数据行,导致业务报错。此时需要快速回退恢复原状。

2.2 回滚逻辑是如何工作的?

gt-checksum 的回滚 SQL 生成采用反向映射策略:对每条修复 SQL,生成一条语义相反的回滚 SQL。

核心映射关系:

修复 SQL 类型 回滚 SQL 类型 说明
DELETE FROM ... WHERE ... INSERT INTO ... VALUES(...) 目标端删除的行,回滚时重新插入
INSERT INTO ... VALUES(...) DELETE FROM ... WHERE ... 目标端插入的行,回滚时删除
TRUNCATE TABLE ... 特殊处理 仅在目标端整表为空时生成(详见 2.3)

DELETE → INSERT 的转换过程:

修复 SQL 中的 DELETE 语句包含 WHERE 条件,其中的列-值对就是被删除行的数据。回滚生成器解析 WHERE 子句,提取所有列名和对应的值,重新组装为 INSERT INTO 语句:

-- 修复 SQL(DELETE)
DELETE FROM `db1`.`orders` WHERE `id` = 1001 AND `order_no` = 'ORD-20260101';

-- 回滚 SQL(INSERT)
INSERT INTO `db1`.`orders`(`id`,`order_no`) VALUES(1001,'ORD-20260101');

对于有 LIMIT 子句的 DELETE(无主键表场景),回滚生成器会先剥离 LIMIT,再提取 WHERE 条件中的列值对。

INSERT → DELETE 的转换过程:

修复 SQL 中的 INSERT 语句包含列名和值的完整映射。回滚生成器解析列名和值列表,仅使用主键列(或唯一键列)构建 DELETEWHERE 条件:

-- 修复 SQL(INSERT)
INSERT INTO `db1`.`orders`(`id`,`order_no`,`amount`) VALUES(1001,'ORD-20260101',99.50);

-- 回滚 SQL(DELETE,仅使用 PK 列 id)
DELETE FROM `db1`.`orders` WHERE `id` = 1001;

NULL 值的特殊处理:

在 WHERE 条件中,NULL 值不能用 = NULL 表达,回滚生成器会自动转换为 IS NULL

-- 修复 SQL
DELETE FROM `db1`.`users` WHERE `id` = 500 AND `email` IS NULL;

-- 回滚 SQL
INSERT INTO `db1`.`users`(`id`,`email`) VALUES(500,NULL);

2.3 TRUNCATE 回滚:整表为空时的特殊处理

当目标端表在校验开始前整表为空(行数为 0)时,修复 SQL 会是大量 INSERT 语句。如果逐行生成回滚 DELETE,文件会非常大。此时 gt-checksum 采用一种更高效的策略:生成单条 TRUNCATE TABLE 回滚语句,忽略 maxRollRowNum 参数限
制。

关键安全约束:只有校验开始前已确认目标端整表为空(精确 COUNT(*) 为 0)时,才会生成 TRUNCATE 回滚。程序不会因为某个 chunk 的目标端为空就推断整张表为空——否则目标端非空但恰好某个 chunk 无数据时,会误生成整表 TRUNCATE
回滚,这将导致回滚时丢失目标端原有的有效数据。

同时,当 TRUNCATE 回滚已生成后,后续的逐行 INSERT 修复对应的回滚 DELETE 会被抑制,避免回滚时先 TRUNCATE 再逐行 DELETE 的重复操作。

⚠️ 重要提醒:TRUNCATE 属于 DDL 操作,执行时会隐式提交当前事务。使用 TRUNCATE 回滚 SQL 前需人工评估其影响。

2.4 回滚 SQL 文件的生成与管理

文件存储结构

回滚 SQL 文件统一存储在 rollFileDir 目录(默认 rollsql)下,文件命名规则为:

rollsql/table.{schema}.{table}.rollback-{TYPE}-{seq}.sql

其中 {TYPE}INSERTDELETETRUNCATE{seq} 为文件序号。例如:

rollsql/table.db1.orders.rollback-INSERT-1.sql
rollsql/table.db1.orders.rollback-DELETE-1.sql
rollsql/table.db1.users.rollback-TRUNCATE-1.sql

文件内容格式

每个回滚 SQL 文件包含完整的事务边界和会话设置:

SET FOREIGN_KEY_CHECKS=0;
SET UNIQUE_CHECKS=0;
BEGIN;
DELETE FROM `db1`.`orders` WHERE `id` = 1001;
DELETE FROM `db1`.`orders` WHERE `id` = 1002;
COMMIT;
BEGIN;
DELETE FROM `db1`.`orders` WHERE `id` = 1003;
COMMIT;

自动滚动切分

当单个回滚 SQL 文件的语句数量或文件大小超过阈值(由 fixTrxNumfixTrxSize 参数控制)时,会自动创建新的文件。这确保了单个文件不会过大,便于人工审计和分批执行。

与 datafix=table 的配合关系

datafix=table(在线修复模式)时,修复 SQL 会直接在目标端执行。但回滚 SQL 始终只写文件,不在线执行——这是刻意的安全设计。回滚 SQL 写入 rollFileDir 目录后,需要人工审计确认无误,再通过 repairDB ./rollsql 执行回滚。

2.5 无主键表的 DELETE 合并优化

对于没有主键和唯一键的表,修复 SQL 中可能出现多条 DELETE ... WHERE ... LIMIT 1 语句,其 WHERE 条件完全相同(因为同一组值可能在目标端出现多次)。gt-checksum 的回滚写入器内置了 mergeDuplicateDeleteLimits 优化:

-- 优化前:3 条相同 WHERE 的 DELETE
DELETE FROM `db1`.`log_data` WHERE `ts` = '2026-01-01' AND `msg` = 'test' LIMIT 1;
DELETE FROM `db1`.`log_data` WHERE `ts` = '2026-01-01' AND `msg` = 'test' LIMIT 1;
DELETE FROM `db1`.`log_data` WHERE `ts` = '2026-01-01' AND `msg` = 'test' LIMIT 1;

-- 优化后:合并为 1 条,LIMIT 累加
DELETE FROM `db1`.`log_data` WHERE `ts` = '2026-01-01' AND `msg` = 'test' LIMIT 3;

这个优化仅在表无主键/唯一键时生效,因为有主键表的每条 DELETE 的 WHERE 条件天然不同。

2.6 与断点续传的配合

resume=ON 且任务中断后续传时,回滚 SQL 的处理同样遵循安全原则:

  • 续传开始时,已有的回滚 SQL 文件会经过完整性截断:定位到最后一个完整的 COMMIT 边界,截断后续不完整的内容
  • 如果续传时某个表需要重新校验,对应的旧回滚 SQL 文件会被清理后重新生成
  • 回滚文件的序号会从上次中断的位置继续,不会覆盖已完整写入的文件

2.7 回滚 SQL 的执行方式

生成回滚 SQL 后,需要人工审计确认无误,然后通过 repairDB 工具执行:

# 审计回滚 SQL 文件
ls -la rollsql/

# 使用 repairDB 执行回滚
repairDB ./rollsql

repairDB 会自动识别目录中的 SQL 文件并按顺序执行,支持断点续传——如果回滚执行过程中断,再次运行会跳过已成功的文件。


三、功能使用演示

3.1 基本配置

在配置文件 gc.conf 中设置相关参数:

# 开启回滚 SQL 生成
genRollSQL=ON
rollFileDir=rollsql
maxRollRowNum=10000

# 其他必要参数
checkObject=data
datafix=file
tables=db1.*
srcDSN=user:ENC[...]@tcp(10.0.0.1:3306)/db1
dstDSN=user:ENC[...]@tcp(10.0.0.2:3306)/db1
chunkSize=10000

3.2 运行效果

$ gt-checksum -c gc.conf

Initializing gt-checksum
Reading configuration files
...
[CHECK] db1.orders: checksum mismatch in chunk 0-10000
[FIX]   db1.orders: DELETE 3 rows, INSERT 5 rows
[ROLL]  db1.orders: Writing rollback SQL (DELETE=5, INSERT=3)
[CHECK] db1.users: checksum mismatch in chunk 0-10000
[FIX]   db1.users: DELETE 1 row, INSERT 0 rows
[ROLL]  db1.users: Writing rollback SQL (DELETE=0, INSERT=1)
...

校验完成后,查看回滚 SQL 目录:

$ ls -la rollsql/
total 24
drwxr-xr-x 2 user user 4096 Jun 15 10:30 .
drwxr-xr-x 6 user user 4096 Jun 15 10:30 ..
-rw-r--r-- 1 user user 1256 Jun 15 10:30 table.db1.orders.rollback-DELETE-1.sql
-rw-r--r-- 1 user user  856 Jun 15 10:30 table.db1.orders.rollback-INSERT-1.sql
-rw-r--r-- 1 user user  312 Jun 15 10:30 table.db1.users.rollback-INSERT-1.sql

查看回滚 SQL 内容:

$ cat rollsql/table.db1.orders.rollback-INSERT-1.sql
SET FOREIGN_KEY_CHECKS=0;
SET UNIQUE_CHECKS=0;
BEGIN;
INSERT INTO `db1`.`orders`(`id`,`order_no`,`amount`) VALUES(1001,'ORD-20260101',99.50);
INSERT INTO `db1`.`orders`(`id`,`order_no`,`amount`) VALUES(1002,'ORD-20260102',150.00);
COMMIT;

3.3 仅对指定表生成回滚 SQL

如果只需要对特定表生成回滚 SQL,可以使用自定义表名模式:

genRollSQL="db1.orders, db1.user%"

这将仅为 db1.ordersdb1.user 开头的表生成回滚 SQL,其他表不生成。支持 % 通配符(匹配任意字符序列)。

3.4 执行回滚

确认需要回退时,使用 repairDB 执行回滚 SQL:

$ repairDB ./rollsql

[REPAIR] Processing: table.db1.orders.rollback-INSERT-1.sql ... OK
[REPAIR] Processing: table.db1.orders.rollback-DELETE-1.sql ... OK
[REPAIR] Processing: table.db1.users.rollback-INSERT-1.sql ... OK
Rollback completed: 3 files executed successfully

3.5 目标端整表为空场景的 TRUNCATE 回滚

当目标端表在校验开始前为空时(例如从零开始的数据迁移验收),回滚 SQL 会生成单条 TRUNCATE TABLE

$ cat rollsql/table.db1.orders.rollback-TRUNCATE-1.sql
SET FOREIGN_KEY_CHECKS=0;
SET UNIQUE_CHECKS=0;
BEGIN;
TRUNCATE TABLE `db1`.`orders`;
COMMIT;

此时不会为该表生成逐行 DELETE 的回滚文件,因为 TRUNCATE 已经足以清空整张表。


四、最佳实践及使用约束

4.1 最佳实践

1. 生产环境建议开启回滚 SQL

对于数据修复任务,无论使用 datafix=file(导出修复 SQL 文件)还是 datafix=table(在线修复),都建议开启 genRollSQL=ON。回滚 SQL 的生成开销很小,但在紧急回退场景下价值巨大:

genRollSQL=ON
rollFileDir=rollsql

2. 大表场景适当调整 maxRollRowNum

maxRollRowNum 用于控制单表待修复行数的回滚阈值。默认值 10000 适用于大多数场景。如果表很大但仍然需要回滚保障,可以调高该值:

# 允许单表最多 50000 行差异时也生成回滚 SQL
maxRollRowNum=50000

注意:maxRollRowNum 对 TRUNCATE 回滚不生效——目标端整表为空时始终生成 TRUNCATE 回滚。

3. 仅对关键表生成回滚 SQL

如果不需要对所有表生成回滚 SQL(例如只关心核心业务表),使用自定义表名模式减少文件量:

genRollSQL="db.orders, db.user%, db.account%"

4. 回滚 SQL 执行前务必人工审计

回滚 SQL 始终只写文件、不在线执行。执行前请:

# 1. 查看生成了哪些回滚文件
ls -la rollsql/

# 2. 检查关键表的回滚内容
cat rollsql/table.db.orders.rollback-*.sql

# 3. 确认无误后使用 repairDB 执行
repairDB ./rollsql

5. 回滚后重新校验

执行回滚后,建议重新运行一次校验,确认数据已恢复到修复前的状态:

# 先清空旧的回滚目录(因为这次不需要回滚了)
rm -rf rollsql/
# 重新校验
gt-checksum -c gc.conf

6. 结合 resume 使用时注意回滚文件累积

断点续传模式下,续传时旧的回滚文件会被安全截断或清理后重新生成。但如果多次中断-续传,可能会产生较多的回滚文件。建议在任务正常完成后整理回滚目录,保留最终版本即可。

4.2 使用约束

1. 仅适用于数据校验模式

回滚 SQL 生成仅在 checkObject=data 模式下生效。struct(结构校验)、trigger(触发器校验)、routine(存储过程/函数校验)不支持回滚 SQL 生成。

2. 必须配合 datafix=file 或 datafix=table

只有当 datafix 设置为 filetable 时,回滚 SQL 才会被生成。datafix=none(仅校验不修复)模式下不会生成回滚 SQL,因为没有修复操作也就不需要回退。

3. rollFileDir 目录非空时的行为

  • resume=OFF 模式:如果 rollFileDir 目录已存在且非空,程序会报错退出,避免覆盖旧的回滚文件
  • resume=ON/ASK 模式:允许目录非空,续传逻辑会处理已有文件(截断或清理)

4. 无主键表的回滚 INSERT 精度限制

对于没有主键和唯一键的表,回滚 INSERT 仅能基于修复 DELETE 的 WHERE 条件中可用的列值对,可能无法完全还原原始行的所有列值(如果 DELETE 语句的 WHERE 条件不包含所有列)。

5. TRUNCATE 回滚的 DDL 隐式提交风险

TRUNCATE TABLE 是 DDL 操作,执行时会隐式提交当前事务并释放表锁。在使用 TRUNCATE 回滚 SQL 时,需确认该行为不会对业务产生意外影响。建议在业务低峰期执行回滚操作。

6. 配置一致性

回滚 SQL 的目标端表名基于校验时的表映射规则。如果校验和回滚之间修改了表映射配置(如 srcdb.*:dstdb.*),可能导致回滚 SQL 中的表名与实际表名不匹配。


五、总结

gt-checksum v4.0.0 的反向回滚 SQL 生成能力,从根本上解决了数据修复"单向执行、无法回退"的痛点。通过 genRollSQL 一键开启,每条修复 SQL 都会自动生成对应的反向操作,写入结构化的回滚文件中。

针对不同场景,回滚机制做了精细化设计:

  • 有主键表:DELETE → INSERT(全列还原),INSERT → DELETE(PK 定位删除)
  • 无主键表:自动合并重复 DELETE LIMIT,回滚 INSERT 基于 WHERE 条件还原
  • 整表为空:生成 TRUNCATE TABLE 回滚,高效且不遗漏

回滚 SQL 始终只写文件、不在线执行,配合 repairDB ./rollsql 即可快速回退。整个过程可审计、可控制,满足生产环境的安全合规要求。

一句话总结genRollSQL=ON,让每一次修复都有后悔药。


相关阅读

posted @ 2026-06-18 10:38  GreatSQL  阅读(0)  评论(0)    收藏  举报