第 11 / 23 篇

第10章:把GTID当作事务身份,而不是数据正确性的证书

文件位置需要同时记住源文件名和偏移,切换源时也容易混淆。GTID为事务提供跨复制流可识别的身份,让副本能声明自己已经执行哪些事务,并由源发送缺少的部分。但一个更好用的定位机制,仍然依赖正确的初始数据和诚实的执行历史。本章明确它能帮助什么,以及不能替你证明什么。

实例身份与事务序列

常见GTID由源UUID与事务编号组成,集合可以表示连续范围与多个来源。server-id用于复制拓扑中的服务器标识,server_uuid用于实例身份相关的事务标记,它们不能随便复制成所有实例同一个值。直接克隆数据目录时尤其需要理解实例身份如何生成与保留。

实验让四个容器从新数据卷初始化,分别使用不同server-id,开启gtid-mode与enforce-gtid-consistency。复制使用SOURCE_AUTO_POSITION=1,由协议按执行集合决定缺失事务。正式报告保存目标时点的源GTID集合,便于把业务状态与事务历史关联起来。

集合包含只说明执行声明

GTID_SUBSET判断某集合是否包含于另一个集合。若结果为真,说明所查询的执行历史包含目标事务身份,不意味着每一张表都和源相同。错误导入、过滤复制、跳过事务、人工本地修改,都会让数据内容与历史之间出现偏差。恢复验证因此不能从“集合相等”直接跳到“账务正确”。

本书wait_replica先等待源当前GTID集合执行完成,然后调用state逐行核对订单、余额与分录。等待保证比较前副本有机会赶上这个静止目标;数据核对证明实际结果。若源还在持续写入,比较必须固定一致视图或选择明确水位,否则两次查询之间的新事务也会制造表面差异。

自动定位需要正确基线

初始数据可以通过一致备份取得,但要让数据范围与GTID声明匹配。比如只导出一个库,却把全服务器GTID集合设为已执行,可能声称自己拥有根本没导入的数据。相反,导入了源数据却没有相应历史,复制可能重复发送同样事务,导致主键冲突或更危险的语义差异。

本书副本从空业务状态开始,在源创建业务表之前就建立复制,所以没有备份初始化的歧义。独立pitr恢复使用set-gtid-purged=OFF,服务于历史恢复验证,不把它直接接回生产拓扑。这两个路径有意分开:我们分别证明复制机制与数据恢复,而不在教学中偷偷合并成一套未经测试的重建流程。

等待函数有时间预算

WAIT_FOR_EXECUTED_GTID_SET可以等待指定集合执行,超时不应被默默忽略。程序限定等待时间,失败时输出复制状态帮助定位。成功返回后仍做业务比较。等待太短可能只是重连退避尚未结束,等待无限长则会让CI卡死且无法区分故障。本书曾实际遇到源重启后的默认重连退避,因此显式缩短并重启接收线程。

这次兼容问题说明,正确的GTID目标并不能消除操作时序。源已经可以SELECT 1,不代表副本接收线程此刻立刻重连。恢复手册应区分服务器就绪、通道连接、日志接收、事务应用和业务验收,不把一个就绪探针当成整条链路完成。

分叉之后不能靠集合并集解决

旧主与新主同时写入,会生成不同来源事务。如果两边修改了同一订单,各自都拥有合法GTID,但合并顺序会影响业务结果。自动定位只知道缺哪些身份,不知道哪笔退款应取消、哪个订单状态更新具有业务优先级。此时需要保存分叉历史并人工判定,不能看到集合互不包含就尝试强行互相复制。

还要警惕“清空GTID历史后重来”。这样可能让原有数据看似没有执行过对应事务,导致重复应用;也可能破坏后续追溯。重置历史是有明确前提的管理操作,不是通用修复按钮。本书不对返回旧主执行这种操作,而是将它隔离,保留差异用于分析。

学会同时读两本账

第一本账是数据库事务历史,回答这个实例声称执行了哪些事务;第二本账是业务记录,回答哪些订单、分录和余额实际存在。恢复工程需要把两者联系起来。只读第一本可能漏掉内容错误,只读第二本可能不知道缺失范围与可继续复制的条件。把源身份、水位和业务摘要一同写入报告,才能为下一次切换留下可信起点。