资讯动态

MySQL GTID复制详解:从一主一从到多源复制的配置与排障

发布时间:2026/10/22 16:24:40 来源:尧图企业网站定制
如果你维护过 MySQL 复制你一定被 binlog 文件名和位点pos折磨过。手动指错一个数字从库就开始报 1236从库挂了一整天主库日志也滚动了好几轮想追上还得先找到正确的位点……这其实就是传统位点复制的日常。GTIDGlobal Transaction Identifier全局事务标识符把每个事务打上全局唯一编号复制链路不再需要手工计算坐标主从切换、级联复制、多源汇总都变得清爽很多。这篇文章我会结合自己实际维护过的环境把 GTID 一主一从、一主多从、多主一从多源复制三种拓扑的配置步骤、关键参数、常见坑全部讲清楚适合刚接触 GTID 的 DBA也适合准备把传统复制迁移到 GTID 的运维同学。1. GTID 到底是什么从传统位点复制说起1.1 传统位点复制的痛点在哪以前配置 MySQL 主从最核心的一条命令是CHANGE MASTER TO后面必须跟两个坐标MASTER_LOG_FILE和MASTER_LOG_POS。坐标对应的意思是“从主库 binlog 的具体文件、具体偏移量开始同步”。这个设计本身没什么问题但实际运维中它有几个非常折磨人的地方第一人工填错坐标。哪怕是经验丰富的 DBA在十几个实例之间切来切去的时候也有可能看错文件或位点一旦填错轻则从库重复执行事务导致主键冲突重则跳过大量事务造成数据不一致而且这种错误往往要过一段时间才能暴露。第二binlog 被清理后坐标直接失效。主库 binlog 保留一般只有几天如果从库宕机时间超过 binlog 保留周期旧坐标对应的日志已经被 purge 掉从库 IO 线程会直接报 1236 错误。这时候没有捷径只能重新做全量备份并重建从库。第三级联复制和主从切换时坐标追踪特别费劲。级联场景下中间从库既要从主库拉日志又要给下一级从库提供日志每层的Master_Log_File和Exec_Master_Log_Pos含义都不同。切换主从时你经常需要推算“新主库在哪个位置开始给新从库喂日志”这是一个非常容易出错的环节。传统位点复制还有一个隐含问题它本身不校验事务内容是否一致。只要各个实例的 binlog 坐标对上MySQL 就会继续执行哪怕实际数据已经因为某个事务被跳过而产生了偏差。1.2 GTID 的核心机制与事务追溯GTID 的全称是 Global Transaction Identifier由两部分组成server_uuid:transaction_id例如3E11FA47-71CA-11E1-9E33-C80AA9429562:23。server_uuid 是每个 MySQL 实例启动时自动生成的全局唯一标识存在auto.cnf文件里transaction_id 是该实例上提交事务的递增序号。因此任何一个 GTID 在整个复制拓扑里都是唯一的不会存在两个不同实例产生相同 GTID 的情况。GTID 的分配有个关键特性只有真正提交成功的事务才会分配 GTID。事务在源库执行时MySQL 内部给它打上 GTID并写进 binlog从库通过 IO 线程拉到这个 binlog 事件后用相同的 GTID 在本地执行。由于 GTID 全局唯一从库天然就知道哪些事务已经执行过、哪些还没执行不会重复应用。这里会涉及两个集合gtid_executed表示当前实例已经执行过的所有 GTID 集合gtid_purged表示已经被 binlog 清理掉、但确实曾经执行过的事务集合。从库建立连接时如果开了MASTER_AUTO_POSITION1它会把自己的gtid_executed发给主库。主库对比自己 binlog 里的 GTID 集合找出从库缺失的那部分只发送这些事务。这套机制彻底替代了“手动指文件指位点”。对比一下传统位点复制差异非常明显对比项传统位点复制GTID 复制同步起点手工指定 binlog 文件偏移量自动协商只拉缺失事务从库跳事务允许但容易造成错乱不支持随意跳过保证一致性级联复制每层都要推算坐标每层自动同步缺失事务主从切换可能要找位点MASTER_AUTO_POSITION1直接切换1.3 开启 GTID 的参数与版本差异开启 GTID 需要修改几个核心 MySQL 参数我一般在my.cnf的[mysqld]段里配置[mysqld] server_id 1001 gtid_mode ON enforce_gtid_consistency ON log_bin mysql-bin log_slave_updates ON binlog_format ROW expire_logs_days 7 max_binlog_size 256M逐个解释一下gtid_modeON是总开关开启 GTID 模式。enforce_gtid_consistencyON强制事务满足 GTID 一致性要求比如不允许在事务里使用临时表非事务性 DDL、不允许更新非事务引擎表MyISAM。这两个参数必须同时开启否则 MySQL 会拒绝启动或报错。log_binmysql-bin是开启 binlogGTID 复制没有 binlog 就没法工作。log_slave_updatesON特别重要它让从库把应用过的事务也写进自己的 binlog。只有开启这个参数从库才能转发日志给下一级从库或者在主从切换后新主库才有完整的 GTID 历史。MySQL 8.0 里log_slave_updates默认是 ON但 5.7 里默认是 OFF我会建议所有节点显式写上避免以后级联或切换时出问题。binlog_formatROW行级复制在 8.0 是默认值5.7 也推荐显式设置。行格式在 GTID 模式下最安全能避免很多函数、触发器引发的数据不一致问题。expire_logs_days7是 binlog 保留天数8.0 里改成了binlog_expire_logs_seconds保留时长一定要结合集群实际情况从库延迟大、恢复慢的环境建议留 15 天以上。如果你是从传统位点复制在线切换到 GTID不能直接把gtid_mode改成 ON。标准流程是先把enforce_gtid_consistency设为 WARN观察日志有没有违规告警再设为 ON然后把gtid_mode依次设为 ON_PERMISSIVE、ON。这一步网上资料很多但我实际建议评估这种切换时直接做一次主从重建比在线切换更干净。2. 一主一从GTID 复制的最小闭环2.1 环境准备与 my.cnf 配置一主一从是最基础的拓扑也是我理解 GTID 复制整个链路的最好入口。假设有两个实例主库 IP 为 192.168.1.10从库 IP 为 192.168.1.11操作系统是 LinuxMySQL 版本建议 5.7 或 8.0 保持一致。如果版本不一致至少要求小版本接近避免 binlog 格式或系统表结构上的兼容性问题。两个实例都提前修改好my.cnf。主库配置示例[mysqld] server_id 101 gtid_mode ON enforce_gtid_consistency ON log_bin mysql-bin log_slave_updates ON binlog_format ROW expire_logs_days 7 max_binlog_size 256M从库配置基本一样唯一的区别是server_id[mysqld] server_id 102 gtid_mode ON enforce_gtid_consistency ON log_bin mysql-bin log_slave_updates ON binlog_format ROW expire_logs_days 7 max_binlog_size 256M为什么要刻意区分server_id因为主库判断一个事务是不是自己产生的除了看server_uuid也会参考server_id。所有复制拓扑节点的server_id都不能相同否则会出现诡异的数据跳过或循环复制。我把每个实例的server_id记录在环境清单里后面每次扩从库都先查清单避免重复。2.2 初始化数据并创建复制账号如果你的主库是全新环境数据量很小可以直接从零开始不需要额外初始化。但生产环境通常主库已经有数据必须先把数据完整搬到从库再开启复制。这里我推荐用mysqldump完成全量导出命令如下mysqldump -uroot -p \ --single-transaction \ --master-data2 \ --set-gtid-purgedON \ --all-databases \ /backup/master_dump.sql关键参数有两个--master-data2会在导出文件头部注释里记录当时主库的 binlog 坐标信息方便你确认时间点--set-gtid-purgedON会在导出文件里生成一条SET GLOBAL.GTID_PURGED...语句这条语句把主库当前已经执行过的 GTID 集合带给了从库从库恢复后才知道“我已经有哪些事务了”。恢复数据到从库mysql -uroot -p /backup/master_dump.sql这里有个常见坑--set-gtid-purgedON的导出文件恢复时如果从库本地gtid_executed不是空的会导致错误。所以从库恢复前要确认这是一个完全干净的实例或者手动重置了 GTID 状态。我自己一般会在恢复前执行RESET MASTER把从库的 binlog 和 GTID 历史清空注意这个命令会清空从库自身的 binlog对新建的从库实例来说是安全的。复制账号在主库上创建建议使用最小权限CREATE USER repl192.168.1.% IDENTIFIED BY StrongPassword_2024; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl192.168.1.%;REPLICATION SLAVE用于建立复制链路REPLICATION CLIENT用于给监控工具查询主从状态。不要用 root 账号做复制权限过大一旦密码泄露整库都完蛋。另外如果主库开启了 SSL可以追加REQUIRE SSL选项这里不展开。2.3 建立复制链路与验证数据初始化完成、账号建好后在从库上执行CHANGE MASTER TO MASTER_HOST 192.168.1.10, MASTER_PORT 3306, MASTER_USER repl, MASTER_PASSWORD StrongPassword_2024, MASTER_AUTO_POSITION 1; START SLAVE; SHOW SLAVE STATUS\G核心是MASTER_AUTO_POSITION 1这告诉 MySQL 用 GTID 自动定位而不是手工指定 binlog 文件与位点。从库会把自己的gtid_executed集合发送给主库主库找出缺失事务后自动推送。MySQL 8.0 里官方开始推荐新语法CHANGE MASTER TO被标记为废弃但还能用。新语法是CHANGE REPLICATION SOURCE TO SOURCE_HOST 192.168.1.10, SOURCE_PORT 3306, SOURCE_USER repl, SOURCE_PASSWORD StrongPassword_2024, SOURCE_AUTO_POSITION 1; START REPLICA; SHOW REPLICA STATUS\G两种语法我用一个表对照免得你在不同版本环境里搞混MySQL 5.7MySQL 8.0/8.4CHANGE MASTER TOCHANGE REPLICATION SOURCE TOMASTER_HOSTSOURCE_HOSTMASTER_LOG_FILE / MASTER_LOG_POSSOURCE_LOG_FILE / SOURCE_LOG_POSMASTER_AUTO_POSITIONSOURCE_AUTO_POSITIONSTART SLAVESTART REPLICASHOW SLAVE STATUSSHOW REPLICA STATUS验证是否同步成功重点关注SHOW REPLICA STATUS\G输出里的几个字段Replica_IO_Running应为 Yes表示 IO 线程正在从主库拉取二进制日志。Replica_SQL_Running应为 Yes表示 SQL 线程正在应用中继日志。Seconds_Behind_Master应为 0 或很小的数字表示从库延迟。Retrieved_Gtid_Set与Executed_Gtid_Set不应为空且Retrieved_Gtid_Set数量应逐渐追上主库的Executed_Gtid_Set。在主库执行SHOW MASTER STATUS\G可以看到Executed_Gtid_Set字段里面记录了主库当前执行过的所有 GTID 集合。从库的Executed_Gtid_Set应该最终包含这些 GTID。这里注意从库的 GTID 集合里会包含自己的server_uuid前缀因为开启 GTID 后从库自身执行事务时也会分配 GTID所以对比时要看集合里的 UUID 成分而不是整串相等。2.4 数据一致性校验与同步监控复制链路建立后最怕的是全量初始化那一刻数据就不一致。我在上线新从库时一定会跑一次数据一致性校验首选工具是 Percona Toolkit 里的pt-table-checksumpt-table-checksum \ --host192.168.1.10 \ --userroot \ --passwordxxxx \ --databasesapp_db \ --replicatepercona.checksums \ --create-replicate-table它会在主库上按行计算校验值然后通过复制把校验结果同步到从库最终对比两边的 checksum。如果出现差异再用pt-table-sync修复。注意pt-table-checksum运行期间会向实例写入 checksum 表建议在业务低峰期执行并且不要一次校验全库分批、按库或按表执行会更稳。日常监控方面我习惯用一条简单的定时任务轮询所有从库的Seconds_Behind_Master同时检查Replica_SQL_Running是否为 Yes。但这里要提一个新手容易忽略的点Seconds_Behind_Master只能反映 SQL 线程应用中继日志的落后程度如果 IO 线程本身拉取慢、或者主库 binlog 生成快于网络传输这个字段可能显示很小甚至为 0实际数据却已经落后很多。更准确的判断是持续对比主库的Master_Log_File / Read_Master_Log_Pos和从库的Retrieved_Gtid_Set差异。3. 一主多从从 1 到 N 的扩展实践3.1 在线新增从库的两种数据初始化方式一主多从最常见的目的是分摊读压力或者给不同业务提供独立的查询实例。新增第二个从库时不需要重复“从主库再导一次全量”有两条成熟路径。路径一直接对主库做一次全新备份方法跟上文一样用mysqldump --single-transaction --set-gtid-purgedON --all-databases或者用物理备份工具比如 Percona XtraBackup做全量备份。物理备份的优势是恢复快尤其数据量上百 GB、上 TB 时逻辑备份的恢复速度完全不可接受。XtraBackup 备份完会在xtrabackup_binlog_info文件里记录对应的 GTID 集合恢复后需要先执行SET GLOBAL.GTID_PURGED...再建立复制链路。路径二从某个现有从库克隆数据。这种方法主库零压力尤其适合主库半夜有大查询、不能接受额外负载的场景。具体做法是停掉一个从库的复制STOP REPLICA拷贝它的数据目录到新机器或者做物理备份恢复。但这里有个非常隐蔽的坑从库的数据目录里包含auto.cnf里面保存了该实例的server_uuid。如果你原样拷贝给新从库两个实例的server_uuid就相同了复制链路会出现无法预料的混乱。解决方案是修改新实例的auto.cnf文件换一个新的 UUID然后重启实例。我个人更喜欢物理备份路线因为逻辑备份在 GTID 模式下还有一个限制--set-gtid-purgedON导出时如果主库的gtid_executed非常大恢复时会用很长时间处理GTID_PURGED。数据量上来之后逻辑备份远不如物理备份实用。3.2 多从库的 server_id 与职责规划如果有三个从库我通常会这样规划节点角色IPserver_idserver_uuid 状态主库192.168.1.10101自动生成唯一从库 1192.168.1.11102自动生成唯一从库 2192.168.1.12103自动生成唯一从库 3192.168.1.13104自动生成唯一server_id必须全局唯一这条是硬性要求。server_uuid通常每个实例自动生成不会冲突但如果你从克隆或模板部署实例一定要检查auto.cnf否则主库会认为两个从库是同一实例日志推送和状态跟踪都会异常。从库职责上建议至少把从库分成两类一类用于线上读流量要保证低延迟、高性能另一类用于备份、报表、数据分析或者大数据平台抽取这类任务往往有重查询会拖累复制线程。不要把备份任务和线上读任务放在同一个从库上这是我在生产环境踩过多次的教训。无论哪类从库只要不承担写职责就建议把参数设为read_only ON super_read_only ONsuper_read_onlyON会连超级管理员账号的普通写入都拦掉只允许复制线程写入。GTID 模式下从库上任何私自写入都很危险后面第 5 章会解释为什么。3.3 级联复制与直接多从的选择从库数量不多时一主多从的星型拓扑最合适主库直接连接所有从库延迟最小架构简单。缺点是每增加一个从库主库就要多发送一份 binlog 数据网络和 IO 开销会线性增长。当从库数量超过 5 到 10 个或者主库出口带宽紧张时就要考虑级联复制。级联复制就是让一个从库兼任下一级从库的主库形成链式结构。配置上中间的从库两个角色都有它相对于上一级是从库相对于下一级是主库。这里log_slave_updatesON就非常重要了——中间节点必须把从上级应用过来的事务重新写进自己的 binlog下一级才能拉到这些事务。如果log_slave_updates没开下一级永远看不到上一级的事务。GTID 模式下级联复制有个明显的优势每一级从库只要设置MASTER_AUTO_POSITION1即可不需要关心中间节点的 binlog 文件和位点。中间节点会精确知道自己执行到哪个 GTID下一级拉取时也会自动从正确的 GTID 位置开始。我实际维护过三层级联结构切换某一级时只需要恢复对应节点的复制线程基本不用手动补数据比传统位点模式省了太多事。不过级联复制也有代价中间节点的故障会影响下一级而且延迟会逐级累积。如果最终目标是做读写分离直接一主多从即可级联更多用于“灾备专线”、“跨机房传输”这类特定场景。3.4 主从切换和高可用上的联动GTID 模式下做主从切换流程比传统位点简单得多核心就两步第一步把旧主库设为只读确保没有新写入第二步把需要提升的新主库执行STOP REPLICA; RESET REPLICA ALL;然后只开放写权限原主库重新配置为它的从库。这里有一个反复出现的细节切换时如果原从库执行了某些本地事务比如手工修改数据它的gtid_executed里就会多出原主库没有的 GTID。切换后新主库作为源库原主库作为从库建立复制时因为 GTID 集合不一致复制线程可能直接卡住或报错。所以服务可用性和数据一致性本质上取决于你对“孤儿事务”的控制程度。这也是为什么我反复强调从库一定要super_read_onlyON。常见的 MySQL 高可用方案比如基于半同步复制的主从自动切换、或基于组复制的多写方案底层都依赖 GTID 来保证事务在集群内不会错乱。如果你现在还在用传统位点模式维护高可用我建议尽早规划迁移到 GTID至少先把新扩容的从库全部切到 GTID 复制再逐步替换存量位点复制。4. 多主一从多源复制的配置与风险控制4.1 什么场景需要多源复制多主一从在官方文档里叫多源复制Multi-Source ReplicationMySQL 5.7 开始正式支持。它的含义是一个从库可以同时从多个主库拉取 binlog 并应用每个主库对应一个独立的复制通道Channel。最典型的业务场景是数据汇总。比如一个公司有订单库、用户库、商品库分别由不同团队维护但数据分析团队需要一个只读汇总库把多个业务库的数据统一到一个实例里做关联查询。如果不用多源复制就得写 ETL 任务定时抽取数据时效性只能做到分钟级或小时级用多源复制从库实时接收所有源库的变更时效性大大提升。另一个常见场景是分库分表后的汇总。业务库按订单号或用户 ID 拆成了多个物理实例但报表系统需要全量数据这时候用多个通道把分片实例全部汇聚到一个从库报表查询只连一台机器即可。还有容灾汇聚场景多个机房各有一套主库在中心机房放一个从库统一拉取作为跨机房的备份汇总节点。4.2 多源复制的配置步骤多源复制的环境准备和一主一从类似所有实例都需要开启 GTID、binlog、log_slave_updates。假设有两个源库源库 A192.168.1.20server_id201管理 orders 库源库 B192.168.1.30server_id202管理 users 库汇总从库192.168.1.40server_id301。每个源库上都要单独创建复制账号授权方式跟前面一样。首次搭建多源复制最重要的一步是分别初始化两个源库的数据到汇总从库。注意两个源库的数据不能直接混在一起导入否则会互相覆盖。我通常的做法是先分别用mysqldump --set-gtid-purgedON导出 orders 和 users 对应的数据库然后在汇总从库上按库依次导入。导入完成后两个源库的 GTID 会分别写入从库的gtid_executed中。接下来配置 channel。在汇总从库上执行CHANGE MASTER TO MASTER_HOST 192.168.1.20, MASTER_PORT 3306, MASTER_USER repl, MASTER_PASSWORD pass_a, MASTER_AUTO_POSITION 1 FOR CHANNEL src_orders; CHANGE MASTER TO MASTER_HOST 192.168.1.30, MASTER_PORT 3306, MASTER_USER repl, MASTER_PASSWORD pass_b, MASTER_AUTO_POSITION 1 FOR CHANNEL src_users;FOR CHANNEL后面是自定义的通道名通常用源库业务含义命名。启动复制时也要指定通道START SLAVE FOR CHANNEL src_orders; START SLAVE FOR CHANNEL src_users;在 MySQL 8.0 下新语法对应为CHANGE REPLICATION SOURCE TO SOURCE_HOST 192.168.1.20, SOURCE_PORT 3306, SOURCE_USER repl, SOURCE_PASSWORD pass_a, SOURCE_AUTO_POSITION 1 FOR CHANNEL src_orders; START REPLICA FOR CHANNEL src_orders;验证状态时需要按通道分别查看SHOW SLAVE STATUS FOR CHANNEL src_orders\G SHOW SLAVE STATUS FOR CHANNEL src_users\G也可以用performance_schema汇总看所有通道的同步情况SELECT CHANNEL_NAME, SERVICE_STATE, RECEIVED_TRANSACTION_SET, APPLIED_TRANSACTION_SET FROM performance_schema.replication_connection_status;多源复制下每个通道的 IO 线程和 SQL 线程是独立工作的。一个通道出问题不会直接阻塞另一个通道这一点在监控时很有用但也意味着你要逐个通道检查不能用一条SHOW SLAVE STATUS就确认全部健康。4.3 多路复用的冲突风险与防护多源复制看起来很美但它本质上是“搬运工”不是“合并器”。如果两个源库往同一个库、同一张表里写数据冲突几乎是必然的。冲突类型主要有三种第一种是主键冲突两个源库分别插入相同主键的数据汇总从库会报 1062 duplicate entry第二种是自增 ID 冲突两个源库各自维护 auto_increment 计数器插入的 ID 很可能撞车第三种是结构冲突两个源库对同一个表做了不同的 DDL 操作汇总从库执行时可能会因为表结构不一致直接报错。我的建议是在开始设计多源复制之前必须先明确划分数据域。最理想的情况是每个源库负责完全不同的数据库或表集合比如源库 A 只写orders库源库 B 只写users库汇总从库上两者互不干扰。如果业务确实存在跨源库相同表名的场景就要使用复制过滤规则将通道限制到具体库或表。搭建 channel 时可以用REPLICATE_DO_DB和REPLICATE_IGNORE_DB指定过滤CHANGE MASTER TO MASTER_HOST 192.168.1.20, MASTER_USER repl, MASTER_PASSWORD pass_a, MASTER_AUTO_POSITION 1, REPLICATE_DO_DB orders FOR CHANNEL src_orders;这里有个经验之谈不到万不得已不要在源库侧做binlog-do-db过滤因为主库过滤会影响 binlog 的完整性后续如果需要做 PITR 或者主库切换缺少的 binlog 事件会带来很大麻烦。尽量在从库侧做通道级过滤保持源库 binlog 完整。4.4 多源复制的监控与故障隔离多源复制的日常监控重点有两个一是所有通道是否都在正常拉取和回放二是各通道之间的gtid_executed是否与对应源库吻合。我建议把performance_schema.replication_connection_status和replication_applier_status_by_worker作为主要监控源封装成脚本定时巡检发现某个通道报错后自动告警。故障隔离方面多源复制很灵活。比如src_orders通道的源库宕机了两小时期间汇总从库的 orders 数据不更新但 users 通道照常同步。恢复后src_orders通道会自动重连并继续拉取前提是源库 binlog 还保留着从库缺失的 GTID 集合。如果宕机时间太长源库 binlog 已经被清理从库重连时会报 1236 错误这时候唯一可靠的解决办法是从某个时间点的备份重建这个通道的数据。实际操作中针对单个通道做重置不需要影响其他通道STOP SLAVE FOR CHANNEL src_orders; RESET SLAVE ALL FOR CHANNEL src_orders;然后重新导入对应源库的全量备份再重新配置该通道。整个过程 users 通道完全不受影响这种按通道隔离的能力是多源复制相比“一个实例拉多份日志再手动归并”的最大优势。5. 常见故障排查与运维经验速查5.1 高频错误对照表GTID 复制虽然比传统位点省心但故障依然存在只是故障形态变了。我把运维中遇到的高频问题整理成了一张速查表方便你直接对照处理错误场景常见错误号原因处置思路主库 binlog 已清理从库重连报错1236从库缺失的 GTID 对应 binlog 已被 purge重建该从库不能用简单跳过事务解决违反 GTID 一致性1786事务中出现临时表或非事务引擎表检查业务 SQL改为事务引擎表或去掉临时表从库回放时主键冲突1062数据初始化时重复或多源复制冲突定位重复数据来源统一数据域设计找不到对应库或表1146复制过滤规则配置错误检查REPLICATE_DO_DB等过滤配置从库本地写入了额外事务切换后无法同步无固定号从库未开启super_read_only出现幽灵事务按 5.2 节处理异常事务relay log 损坏或与 GTID 状态不一致1872强制 kill 或磁盘异常停止复制重置 relay log必要时重建复制1236 错误是 GTID 模式下最常见的崩溃点报错信息通常会提到 “the master has purged binary logs containing GTIDs that the slave requires”。这种情况说明从库落后的时间已经超过了主库 binlog 保留周期只能用新的全量备份重新初始化从库。不要尝试通过SET GLOBAL.SQL_SLAVE_SKIP_COUNTER来跳过GTID 模式下这种操作基本无效强行跳只会让数据偏差更大。5.2 幽灵事务的处理幽灵事务也叫 errant transaction指从库自己执行了一些不在主库 binlog 里的事务。最常见的来源是排障时的临时手动写入从库报 1062 主键冲突DBA 为了让复制继续手工在从库上执行一条 UPDATE 或 DELETE或者从库开启了事件调度器Event Scheduler定时任务在从库本地写数据。为什么说它危险因为 GTID 复制讲究的是“全局唯一且完整”。从库上手工写入的事务会获得一个本机生成的 GTID并记录进从库的gtid_executed。当这个从库被提升为新主库时它的gtid_executed里有一条旧主库没有的事务旧主库重新作为从库后会尝试从新主库拉取这条事务来执行但这条事务在新主库的 binlog 里根本不存在因为它只是旧从库的本地写入于是复制再也对不上而且这种问题很难一眼发现。处理幽灵事务的标准方法有两种。第一种如果幽灵事务本身不涉及实际数据变化可以直接通过注入空事务来补齐 GTIDSET GTID_NEXT 幽灵事务的完整GTID; BEGIN; COMMIT; SET GTID_NEXT AUTOMATIC;这里的GTID_NEXT在 MySQL 5.7 和 8.0 中都可以手动设置允许你“骗”过系统让目标实例认为这个 GTID 已经应用过。第二种如果幽灵事务确实改了数据就需要在新主库上补做相同的 DML保持数据一致后再注入 GTID。预防永远比处理更重要。我的经验是所有从库一律开启super_read_onlyON除非有非常明确且经过审批的临时操作否则任何本地写入都拒绝。事件调度器也要检查一遍确认event_scheduler是否开启是否只在主库运行。5.3 GTID 与备份恢复的相互影响GTID 模式下的备份恢复比传统位点多了一个需要特别关注的地方备份文件必须携带 GTID 信息否则恢复后的实例无法直接建立复制链路。使用mysqldump时务必带上--set-gtid-purgedON。恢复这个导出文件后实例的gtid_executed会继承源库的 GTID 集合然后执行CHANGE MASTER TO ... MASTER_AUTO_POSITION1就能直接同步。如果你漏掉了这个参数或者用了--set-gtid-purgedOFF恢复后的实例gtid_executed为空建立 GTID 复制时会因为集合不匹配而失败。使用 XtraBackup 等物理备份工具时备份完成后会生成xtrabackup_binlog_info文件记录了备份点对应的 binlog 文件名、偏移量和 GTID 集合。恢复备份后在配置复制前需要先设置GTID_PURGEDSET GLOBAL.GTID_PURGED 源库的gtid集合;注意执行SET GTID_PURGED前目标实例的gtid_executed必须为空否则会报错。所以从备份恢复的目标实例要先确认没有历史复制状态必要时执行RESET MASTER清空。另一个常见场景是误删除数据后的 PITR时间点恢复。GTID 模式下做 PITR 虽然可以使用 binlog 内容但恢复工具最终还是要回到 binlog 的 filepos 坐标来控制恢复截止点因为 GTID 是按事务粒度标识的做不到“恢复到某个 GTID 之前”的精确语义。很多 DBA 第一次接触 GTID 时以为恢复也能完全丢弃坐标实际不是。备份策略里还是要保留 binlog 对应的时间点和坐标信息作为 PITR 的恢复边界。5.4 运维工具箱与个人体会这里分享几个我日常在用、觉得非常实用的运维手段。第一个是快速检查所有通道健康状态的 SQLSELECT CHANNEL_NAME, SERVICE_STATE, IO_THREAD_STATE, SQL_THREAD_STATE, LAST_ERROR_NUMBER, LAST_ERROR_MESSAGE FROM performance_schema.replication_connection_status;这个查询比逐个执行SHOW REPLICA STATUS高效很多配合脚本可以快速定位是哪个通道、哪个环节出了问题。第二个是判断主从是否追平的简易逻辑。在主库执行SHOW MASTER STATUS\G拿到当前的Executed_Gtid_Set再从库执行SHOW REPLICA STATUS\G拿到Retrieved_Gtid_Set和Executed_Gtid_Set对比两者差异。如果长时间有差异且Seconds_Behind_Master持续增长优先怀疑网络带宽或从库磁盘性能而不是一味加大 binlog 保留时间。第三个是我个人很推荐的习惯每个复制拓扑节点都维护一份实例清单记录 IP、端口、server_id、server_uuid、角色、所属通道、负责人。加库、切主、重建复制节点之前先翻清单能避免很多低级错误。我经历过一次因为两台机器server_uuid相同导致复制数据串位的故障排查过程极痛苦最后发现是模板虚拟机克隆时把auto.cnf带过去了。从那以后每一台新建实例都会检查auto.cnf并把这些信息记录到清单里。最后说点个人体会。GTID 解决了复制坐标系的问题但它没有解决复制本身的全部问题binlog 保留策略不合理照样报 1236从库被偷偷写入照样产生幽灵事务多源复制数据域划分不清照样主键冲突。GTID 更像是把原本需要人工计算和拼凑的“位置”变成了自动协商的“事务集合”让我们能把精力放在数据一致性、容量规划和高可用架构上。如果你现在还在用传统位点复制我建议从最小的一个从库开始切换到 GTID跑一段时间熟悉这套事务协商机制后再逐步改造核心链路。我自己从传统复制迁移到 GTID 之后最大的感受不是操作变少了而是出错空间变小了。以前切换主从总得反复核对 binlog 坐标心里没底现在只要所有节点都开了GTID_MODEON、MASTER_AUTO_POSITION1切换就是在确认数据追平后的一键操作。当然前提是你把上面这些参数、备份、只读保护都做到位。GTID 不会替你思考但它可以把那些最容易出错的计算环节摘掉这就是它最值得的地方。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑