
很多做数据库运维或者后端开发的朋友迟早会碰到主从同步的需求要做读写分离、要做高可用、要做数据灾备MySQL 主从复制都是绕不开的基础环节。而 GTID 主从模式相比传统基于 binlog 文件名加 Position 位点的方式在运维便捷性和一致性保障上都有明显优势。今天这篇就专门聊 MySQL 8.0 环境下GTID 主从模式怎么一步一步搭起来中间哪些坑是文档里不会明说的我会一次性讲清楚。这篇内容适合刚接触 MySQL 复制的初学者也适合已经搭过传统主从、但想切换思路的老手。我尽量把原理和实操结合起来讲先说明白 GTID 到底是什么、解决了什么痛点再带你把完整的搭建流程走一遍最后会把常见报错和排查思路整理成速查表方便你实际操作时对照使用。1. 为什么主从复制要选 GTID 方案1.1 传统主从复制的痛点在哪里传统的主从复制方案从库需要记住三个关键信息主库的 binlog 文件名、binlog 内的 Position 位点以及主库的 server_id。配置好之后从库通过 IO 线程拉取主库 binlog再通过 SQL 线程在本地回放。这套机制本身很成熟但用久了问题也很明显。首先是位点容易失灵。主库一旦发生宕机、切换或者 binlog 被清理从库记录的位点可能就失效了。更麻烦的是如果你手动去指定位点一不小心指定错了比如记错了 Position或者中间跳过了一段 binlog主从数据就悄悄不一致了。这种问题很难及时发现等发现的时候往往已经晚了需要重新全量重建从库才能修复。其次是切换过程繁琐。传统模式下做主从切换需要登录从库去查看 Relay Log 的执行位点再去新主库上找对应的 binlog 位点人为计算、手工填写过程容易出错。如果是跨机房场景或者在多个从库之间做 failover这套流程的运维成本会成倍增加。还有一点容易被忽略传统复制模式下同一个事务在每台机器上的 binlog 文件位置是各不相同的定位一个事务必须依赖文件名位点二元组。这把事务的标识和具体文件绑死了文件一清理、一改名这条事务在历史上到底存不存在你根本说不清楚。1.2 GTID 是如何解决这些问题的GTID 的英文全称是 Global Transaction Identifier即全局事务标识符。它的核心思路是给每一个提交的事务分配一个全局唯一的编号格式是server_uuid:transaction_id比如3f1c2a5e-8d44-11ec-b0d0-00163e0a5d2e:1。这个编号在源库生成后会随着事务一起写入 binlog从库在回放时也会记录相同的 GTID。也就是说同一个事务在整个复制链路中无论走到哪台机器它的 GTID 都是完全一致的。这样一来事务的身份就和 binlog 文件的位置彻底解耦了。GTID 模式下从库不需要再关心 binlog 文件名和 Position 位点。搭建复制时只需要执行一条CHANGE MASTER TO MASTER_AUTO_POSITION1从库会自动和主库协商告诉主库自己已经执行过哪些 GTID主库只把缺失的 GTID 事务发给从库。这个交互过程由 MySQL 内部的协议自动完成不需要人肉计算位点。这种设计的优势在故障切换时体现得最明显。主库挂了任意选一个从库晋升为主库其他从库直接指过去即可GTID 会自动补齐缺失事务不用担心位点对不上。这也是为什么很多高可用方案包括 MGR、Orchestrator 等都要求底层开启 GTID 模式因为 GTID 让复制关系的维护变成了一件确定性的事情。1.3 什么时候选择 GTID什么时候继续用传统复制GTID 虽然好但也并非所有场景都适合无脑切换。我个人建议只要是新部署的主从环境一律优先使用 GTID 模式。8.0 版本里 GTID 已经默认开启说明官方也在推动这个方向。不过有几个遗留场景需要注意。如果你有一些老系统从库中用到了sql_slave_skip_counter来跳过错误事务或者有系统依赖 binlog 文件Position 做日志订阅和增量解析比如某些自研的 CDC 组件迁到 GTID 模式前需要先评估兼容性。GTID 模式下跳过事务的方式和传统模式不一样不是靠sql_slave_skip_counter而是通过注入空事务来实现原理和使用方法我会在后面的问题排查部分详细展开。提示MySQL 5.6 开始支持 GTID5.7 版本成熟可用8.0 版本默认开启。如果你的线上还在用 5.7也不必担心GTID 功能完全兼容。2. 搭建前的环境规划与基础准备2.1 服务器规划两台机器怎么分配合适我习惯用一个最小化环境来演示和验证架构就是经典的一主一从。角色hostnameIP 地址操作系统MySQL 版本主库 Masterdb-master192.168.1.10CentOS 7.98.0.32从库 Slavedb-slave192.168.1.11CentOS 7.98.0.32生产环境建议至少三台机器起步一台主库、两台从库。一主一从只能保证数据有备份但主库故障后单从库晋升新的主库没有下游备机会存在一段时间的单点窗口。我见过不少团队在业务早期就搭一主一从结果主库硬件故障短信半夜响才发现备机还没准备充分那叫一个被动。如果你准备从零开始上生产我的建议是一个主库、两个从库或者直接考虑 MySQL 官方推荐的 InnoDB Cluster基于 MGR。但无论哪种架构先把 GTID 复制的原理和搭建流程吃透都是必要的基础功。2.2 磁盘与目录规划数据目录和 binlog 分开放MySQL 8.0 的数据目录默认在/var/lib/mysqlbinlog 默认也存放在同一块磁盘上。这在小数据量下没什么问题但生产环境我强烈建议把数据目录和 binlog 分开挂载。原因有两个第一性能隔离。binlog 写入是顺序写数据文件写入是随机写InnoDB 的 DoubleWrite、redo log 也在动。如果共用一块盘磁盘的 IO 调度会被两种不同特性的写入互相干扰高峰时期容易出现性能抖动。第二容量规划。binlog 的膨胀速度有时候非常夸张特别是当表上有大量 UPDATE 或 DELETE 操作时。如果 binlog 和数据目录挤在同一块盘上binlog 涨满后整个 MySQL 会直接 hang 住等你发现时可能连登录去清理的余地都没有了。我在生产上一般这样划分数据目录放一块高 IO 的 SSDbinlog 放一块容量较大的普通 SSD 或 HDDredo log 单独一块高性能小容量 SSD。这是成本与性能的折中如果你预算有限至少也要做到数据与 binlog 分盘。2.3 安装 MySQL 8.0yum 源安装还是二进制部署MySQL 8.0 的安装方式有好几种。CentOS 7 环境下最省事的是用官方 yum 源一条命令安装到位。如果你的环境无法联网再用二进制包手动部署。这里特别提醒一个问题生产环境千万不要安装系统自带的 MariaDB。CentOS 7 默认仓库里的mysql包其实是 MariaDB和 MySQL 的目录结构、配置项有差异混用容易出现不明问题。安装前先用rpm -qa | grep -i mariadb检查有的话先卸载。安装完成后先启动服务并查看初始密码。8.0 初始化时会在日志里生成一个临时密码用下面的命令拿到grep temporary password /var/log/mysqld.log拿到临时密码后用mysql -uroot -p登录执行ALTER USER修改 root 密码。8.0 默认密码策略要求包含大小写字母、数字和特殊字符长度至少 8 位这个策略可以按需调整但我不建议在生产环境调弱。2.4 关键配置参数my.cnf 里必须要有的几项先看主库配置。GTID 模式有四个核心参数gtid_mode必须为ONenforce_gtid_consistency必须为ON。前者决定启用 GTID后者确保所有事务都是 GTID 安全的——如果不开启这个参数某些非事务性操作可能会产生没有 GTID 的事务导致复制链路出现空洞。另外两个参数是log_bin和server_id前者开启 binlog 日志后者给实例一个全局唯一标识。server_id必须唯一多台实例之间不能重复否则主从会串数据。这一点怎么强调都不过分我见过太多新手在同一套环境里两台机器都默认server_id1然后主从一直报server_id冲突的错。从库除了上面这些参数以外还需要额外配置relay_log和read_only。relay_log是中继日志从库的 IO 线程把主库 binlog 拉到本地后先写入 relay log再由 SQL 线程回放。read_only是为了防止从库被误写入数据。开启read_only后普通用户只能读不能写但超级管理员例外所以 DBA 的账号仍然可以在从库操作需要你自律了。提示8.0 中default_authentication_plugin默认是caching_sha2_password创建复制专门账号时要注意连接认证问题。MySQL 8.0 的复制账号建议沿用默认插件不要去改成mysql_native_password除非你的客户端确实不支持新插件。这一点很多人不知道后面创建账号时会再强调。3. GTID 主从搭建实操全流程3.1 第一步修改主库配置并初始化数据先编辑主库的配置文件我一般放在/etc/my.cnf根据不同场景按下面的模板逐项核对[mysqld] server_id 1 port 3306 log_bin /data/mysql/logs/binlog/mysql-bin binlog_format ROW gtid_mode ON enforce_gtid_consistency ON binlog_expire_logs_seconds 604800几个参数逐个说。binlog_format ROW8.0 默认就是 ROW 格式这是官方推荐做法。ROW 格式下 binlog 记录的是每一行数据的前后镜像不会出现 statement 格式下部分函数在主从执行结果不一致的问题。虽然 ROW 格式日志量会大一些但一致性收益远大于代价。binlog_expire_logs_seconds 604800设置 binlog 保留 7 天单位是秒。8.0 中expire_logs_days已废弃用这个参数来替代。保留时长取决于你的从库同步延迟和全量备份策略如果从库经常有延迟或者需要用 binlog 做时间点恢复保留时间可以适当延长。配置完成后启动 MySQLsystemctl start mysqld systemctl enable mysqld随后登录 MySQL验证 GTID 相关参数是否生效mysql SHOW GLOBAL VARIABLES LIKE gtid_mode; ---------------------- | Variable_name | Value | ---------------------- | gtid_mode | ON | ----------------------3.2 第二步创建复制专用账号复制账号不能直接用 root这是基本的安全底线。原因很简单CHANGE MASTER TO的位点信息、binlog 拉取权限、超级权限都集中在账号权限里如果这个账号泄露攻击者可以直接控制整个复制链路甚至通过注入事务污染从库数据。创建账号并授权mysql CREATE USER repl192.168.1.% IDENTIFIED BY Repl_123456; mysql GRANT REPLICATION SLAVE ON *.* TO repl192.168.1.%;这里%指的是从库所在网段。严格按环境配置不要用repl%这种全开放写法从安全角度讲权限范围越窄越好。连接权限限制在从库所在的网段即使账号密码泄露攻击者也无法从其他 IP 发起连接。这是我做任何数据库账号管理的默认习惯。3.3 第三步准备一套全量备份并恢复到从库主从复制的前提是从库有一份和主库一致的历史数据快照。如果从库是空库那直接跳过恢复步骤从第一个 binlog 开始同步即可。但生产环境基本不存在空库所以这里必须处理数据初始化。备份工具有很多mysqldump适合小数据量几个 GB 以内Percona XtraBackup或者 MySQL 8.0.17 自带的 Clone Plugin 适合大数据量。mysqldump导出时必须要加--single-transaction --set-gtid-purgedON这样才能在备份文件中带上 GTID 信息。--single-transaction通过开启一个事务来实现一致性快照不加此项会锁表影响线上写入。mysqldump -uroot -p --single-transaction --set-gtid-purgedON --all-databases --master-data2 /tmp/fullbackup.sql在主库上执行导出然后把备份文件传到从库scp /tmp/fullbackup.sql 192.168.1.11:/tmp/在从库上恢复mysql -uroot -p /tmp/fullbackup.sql恢复完成后需要重点确认一个信息备份文件里的 GTID_PURGED 内容。这个内容代表主库到目前为止已经执行过哪些事务从库恢复后会把这些 GTID 标记为已执行后续MASTER_AUTO_POSITION1时主库就不会再把已执行的 GTID 重复发送。3.4 第四步从库配置与启动复制通道修改从库配置文件重启 MySQL[mysqld] server_id 2 port 3306 log_bin /data/mysql/logs/binlog/mysql-bin binlog_format ROW gtid_mode ON enforce_gtid_consistency ON relay_log /data/mysql/logs/relaylog/relay-bin read_only ON登录从库执行复制通道建立命令mysql CHANGE MASTER TO MASTER_HOST192.168.1.10, MASTER_PORT3306, MASTER_USERrepl, MASTER_PASSWORDRepl_123456, MASTER_AUTO_POSITION1;这里的关键是MASTER_AUTO_POSITION1。只要设置了这一项MySQL 会自动执行 GTID 协商不需要再指定MASTER_LOG_FILE和MASTER_LOG_POS。传统模式下这两个参数是最容易出错的点GTID 模式下它们彻底退出了复制搭建流程。然后启动复制mysql START SLAVE;查看复制状态mysql SHOW SLAVE STATUS\G重点看几个字段Slave_IO_Running: YesIO 线程正常Slave_SQL_Running: YesSQL 线程正常Seconds_Behind_Master: 0从库与主库的延迟0 表示完全同步Retrieved_Gtid_Set已经拉取到的 GTID 集合Executed_Gtid_Set已经执行完成的 GTID 集合如果Slave_IO_Running和Slave_SQL_Running都是 Yes说明主从链路已经通了。3.5 第五步验证复制是否真正生效很多人启动完复制看到两个线程是 Yes 就觉得大功告成。我会说别急先做一遍验证。在主库上创建一张测试表并插入数据mysql CREATE DATABASE testdb; mysql USE testdb; mysql CREATE TABLE t1 (id INT PRIMARY KEY, name VARCHAR(50)); mysql INSERT INTO t1 VALUES (1, gtid-test);然后在从库上查询mysql USE testdb; mysql SELECT * FROM t1; --------------- | id | name | --------------- | 1 | gtid-test | ---------------数据能查到说明这条事务已经通过 GTID 通道完整同步到了从库。此时再回去看看从库的Executed_Gtid_Set你会看到类似这样的内容3f1c2a5e-8d44-11ec-b0d0-00163e0a5d2e:1-5这个集合表示主库上有 5 个事务已经执行完毕。以后排查数据一致性问题Executed_Gtid_Set是最关键的定位入口——从库执行到哪了、漏了哪些事务一目了然。注意GTID 复制链路建立成功之后不建议在主库上执行RESET MASTER。这个命令会清空 binlog 并重置 GTID 执行记录但已经下发到从库的 GTID 信息不会跟着重置链路随时可能断裂。4. 运维中的高频问题与排查经验4.1 UUID 冲突主从复制最常见的翻车现场如果你在SHOW SLAVE STATUS中看到类似这样的报错Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs说明主从两台机器的server_uuid重复了。server_uuid存储在数据目录的auto.cnf文件中如果你是用克隆虚拟机、镜像恢复或者直接拷贝数据目录的方式搭建环境两台机器很可能带着同样的auto.cnf启动于是 UUID 完全一致。排查方法很简单cat /var/lib/mysql/auto.cnf分别登录主库和从库查看mysql SHOW GLOBAL VARIABLES LIKE server_uuid;如果确认一致在从库上停止 MySQL删除auto.cnf重启即可。MySQL 重启时会自动生成一个新的 UUID复制链路就能恢复了。systemctl stop mysqld rm -f /var/lib/mysql/auto.cnf systemctl start mysqld这个坑在虚拟化环境里特别容易踩搭建复制之前养成先检查两边 UUID 的习惯能省下不少排查时间。4.2 SQL 线程报错事务冲突与 1062 重复键错误GTID 模式下SQL 线程最常见的报错是 1062Duplicate entry也就是从库回放时发现主键或唯一键冲突。产生原因通常有两个一是恢复备份时重复插入了数据。前面说备份文件里带了GTID_PURGED恢复过程中如果重复执行了某一段数据会引起主键冲突。这种情况重做恢复流程即可。二是从库被误写入了数据。虽然read_onlyON限制了普通用户写入但如果有人用超级管理员账号在从库上执行了 INSERT破坏了主从数据一致性SQL 线程回放到这一行时就会报 1062。遇到 SQL 线程报错时先查看具体错误信息mysql SHOW SLAVE STATUS\G找到Last_SQL_Error字段显示的出错语句。如果确认是误操作导致的从库多余数据手动删除从库对应记录然后执行START SLAVE恢复。如果主库和从库的数据差异已经很大最稳妥的方案是跳过冲突事务或者重新搭建从库。注意一点GTID 模式下不能再使用sql_slave_skip_counter这个参数在 GTID 模式下直接不可用。正确做法是注入一个空事务让 GTID 继续前进mysql STOP SLAVE; mysql SET GTID_NEXT3f1c2a5e-8d44-11ec-b0d0-00163e0a5d2e:6; mysql BEGIN; mysql COMMIT; mysql SET GTID_NEXTAUTOMATIC; mysql START SLAVE;这里的3f1c2a5e-8d44-11ec-b0d0-00163e0a5d2e:6就是出错事务的 GTID先用空事务占位让它执行的 GTID 集合往前推进再启动 SQL 线程去处理后面的日志。提醒注入空事务是跳过错误的应急操作不是修复一致性的手段。跳过事务意味着从库这个 GTID 对应的变更没有真正执行数据仍然是缺的。遗漏的那部分数据要在业务空闲期用pt-table-checksum和pt-table-sync这类工具做一次校正。跳过事物一时爽数据校验不能忘这条经验我是在生产环境踩过坑后总结出来的。4.3 主从延迟持续攀升怎么定位GTID 模式下主从延迟依然可能出现表现是Seconds_Behind_Master数值不断增大或者 SQL 线程一直显示追日志的状态。定位方向我一般按三步走第一步看 IO 线程是否拉日志正常。如果Seconds_Behind_Master为 NULL先检查 IO 线程状态。网络带宽波动或者主从之间防火墙对超大包做了限制都有可能导致 IO 线程拉取速度变慢。第二步看库上是否存在大事务。GTID 是事务级的复制粒度一个事务要全部执行完SQL 线程才能继续处理下一个。如果主库上有一次 UPDATE 影响了几百万行从库回放时需要的时间可能比主库执行还长。这种延迟即使追到凌晨也追不上因为它不是一个缓慢堆积的问题而是一个单体大任务卡住了。第三步看从库自身的配置。从库的磁盘 IO 能力、innodb_flush_log_at_trx_commit、sync_binlog的参数调整都会显著影响回放速度。如果条件允许可以考虑开启并行复制。8.0 中默认的并行复制策略是根据 Writeset 来并行应用事务的效果比拆库拆表的方案好很多。在从库配置中添加或修改这些参数binlog_transaction_dependency_tracking WRITESET slave_parallel_type LOGICAL_CLOCK slave_parallel_workers 8slave_parallel_workers的取值要根据从库的 CPU 核数来定8 核机器我一般给 4 到 8 个。给的太多线程切换开销会抵消并行收益甚至更慢。修改配置后重启 MySQL 或者动态修改参数生效mysql STOP SLAVE; mysql SET GLOBAL slave_parallel_workers 8; mysql START SLAVE;4.4 复制链路异常时的快速排查顺序整理一个速查思路按照下面的顺序逐项检查大部分问题五分钟之内能定位。排查项检查方法常见状态网络连通性telnet 192.168.1.10 3306通/不通复制账号权限从库用repl账号手动连接主库能/不能连接server_id 唯一性分别查主从的 global server_id用 concat 拼接即可server_uuid分别查主从的 auto.cnf必须不一致binlog 是否开启主库SHOW GLOBAL VARIABLES LIKE log_binON/OFFGTID 参数生效主从库都检查gtid_mode与enforce_gtid_consistency必须同为 ON防火墙放行主库防火墙放行 3306放行/未放行从库read_only从库SHOW GLOBAL VARIABLES LIKE read_onlyON 为正常我一般建议把这条排查顺序贴在 OnCall 文档里每次出现主从告警就按这个来节省大量临场判断时间。4.5 长期运维中容易忽视的细节主从链路搭通了日常运维也不能松懈。有几个容易被忽视的细节我根据自己的实战经验提醒一下。第一个是磁盘空间监控。从库的 relay log 在 IO 线程拉取日志后、SQL 线程回放前的窗口期会暂存在本地。如果 SQL 线程长时间宕住relay log 会一直堆积直到把磁盘打满。一定要对从库的 relay log 目录做容量监控磁盘使用率达到 80% 就要告警。第二个是主库 binlog 保留策略。GTID 复制模式下如果从库停机太久主库上对应的 binlog 已经被清理从库重新启动后会发现缺失的 GTID 找不回来需要重建从库。binlog 保留时长至少要覆盖全量备份周期加一个业务高峰期同时要保证从库最长停机时间不超过 binlog 窗口。第三个是周期性校验数据一致性。GTID 只保证事务被按序执行不保证事务执行结果正确。从库回放过程中可能因为硬件错误、人为误操作等因素产生数据偏差。建议每月跑一次pt-table-checksum校验主从一致性发现偏差再用pt-table-sync修复。我从不在没有校验过的同步环境上直接做主从切换这条原则已经帮我挡住了好几次事故。5. 更进一步的方案从一主一从到组复制与容器化部署GTID 的主从复制搭建起来后可以实现数据备份、读写分离和基本的故障切换。但一主一从在高可用上依然有短板主库宕机时只能手动把从库提升为主库期间业务会有明显的不可用窗口。为了解决这个问题MySQL 官方推荐的方向是 InnoDB Cluster底层依赖 MySQL Group Replication组复制。组复制基于 Paxos 协议实现数据一致性多个节点组成一个组任何节点写入的数据都会在组内多数派节点上确认后才返回成功。相比传统主从复制组复制具备自动故障检测和自动主节点选举能力故障切换时间能控制在秒级。我个人对组复制的建议是业务对可用性要求高或者团队 DBA 人力紧张优先考虑 InnoDB Cluster。如果只是做读写分离和数据灾备一主两从的 GTID 复制依然够用成本和复杂度都更低。另外一个值得留意的方向是容器化部署。MySQL 8.0 在 Docker 环境下的 GTID 主从搭建思路和物理机完全一致只是配置文件和数据目录变成了挂载卷的方式。用 docker-compose 编排时只要给每个实例单独的配置文件和命名卷逻辑和上面的步骤没有区别。容器化的好处是环境复现快、团队协作方便但要注意数据持久化不能落下容器销毁重建后数据丢失是新手最容易踩的坑。写在最后搭好主从之后运维才算刚刚开始GTID 主从模式搭建本身并不复杂流程理顺了从准备到验证半小时内能完成。但真正考验经验的地方在日常运维是那些看似不起眼的细节UUID 冲突、binlog 保留时长、relay log 堆积、切换前的数据校验。这些坑我基本都踩过一轮写出来也是希望你能少走弯路。个人经验上我特别推荐拿到任何一套新环境都先把SHOW SLAVE STATUS的几项关键指标弄明白再动手修改配置。搭建之前花十分钟做一次环境巡检比出问题时熬一晚上排查要划算得多。主从复制只是高可用体系的起点但不是终点。每次遇到问题都值得把根因吃透再沉淀到团队的运维文档里这样的积累才能在真正的大故障来临时不慌。