ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

MySQL 8.0 GTID主从复制原理与实战:从机制到故障排查一次讲透

MySQL 8.0 GTID主从复制原理与实战:从机制到故障排查一次讲透 我先把话说在前头如果你还在用基于日志文件名和Position的老式主从复制在MySQL 8.0时代是真的有点跟不上节奏了。不是说老方案不能用而是GTID模式带来的省心和可靠几乎让你找不到理由拒绝它。这篇文章我就拿一套完整的MySQL 8.0一主一从环境把GTID主从从原理到实操再到我踩过的坑一次讲透。这篇内容适合谁主要三类人刚接触MySQL复制的运维新手被公司老架构里一堆主从问题折磨的开发以及想把手上的MySQL实例从位点复制平滑切到GTID模式的DBA。不管你是哪一类照着这篇文章的思路和命令走基本不会走偏。1. 为什么老方案让人失眠传统位点复制到底差在哪里我先聊聊传统复制模式的痛点不然你体会不到GTID的好。1.1 你被文件名和Position支配过吗传统主从复制里从库要同步主库的数据靠的是三个关键信息binlog文件名、binlog文件里的偏移量Position以及主库的server-id。从库发起复制时执行CHANGE MASTER TO把MASTER_LOG_FILE和MASTER_LOG_POS这两个值填上复制链路才算建立。听起来没什么问题是吧但问题恰恰就藏在这里。第一位置信息是脆弱的。主库一旦做了日志清理PURGE BINARY LOGS、切换了binlog文件或者你手动执行过RESET MASTER那个旧的Position可能就失效了。从库重新连接时主库根本找不到那个偏移量对应的日志位置复制直接断掉。第二找位点非常反人类。你可能经历过这种场景主库挂了从库要提升为新主库结果另外几台从库需要重新指向它。这时候你得登录到新主库上执行SHOW MASTER STATUS盯着那个binlog文件名和Position数字再跑到其他从库一台台去改。而且表结构如果有变更、事务交错执行你就算找到了位点也说不定从库一启动就报错。第三位点模式下没法快速判断主从数据是否一致。尤其当复制链路比较复杂比如一主多从、级联复制A→B→C的时候每个节点都得维护自己的文件名和位置任何一个节点出了问题排查链路都够你喝一壶。1.2 GTID解决的正是这样的问题GTID的全称是Global Transaction Identifier全局事务标识符。它的核心思路是不再用文件偏移量这种物理位置来定位同步进度而是给每一个事务分配一个全局唯一的ID。复制过程变成了基于事务ID的传递从库只要知道自己执行到哪个GTID了就能直接从主库拉取后续事务。打个比方你就懂了。传统方式像是你在一本书里夹书签书页一撕、目录一改书签的位置就可能对不上了GTID方式则是给每一页都编上了唯一的页码不管书本怎么重新装订你只要告诉别人我看到第N页了对方就知道接下来该给你哪些内容。从MySQL 5.6开始引入GTID到5.7逐步成熟再到8.0里彻底稳定GTID已经成为官方推荐的复制方案。8.0里做Replica Set、InnoDB Cluster这些高可用方案底层清一色是GTID模式。你要是不会GTID后面玩MGRMySQL Group Replication也没戏。2. GTID的运转机制从事务诞生到全局追踪你光会执行几条命令不够得理解GTID在底层是怎么跑起来的不然以后遇到报错照样两眼一抹黑。2.1 一个GTID长什么样GTID的格式非常直白source_id:transaction_idsource_id生成该事务的服务器标识实际就是服务器的server_uuid在auto.cnf文件里路径一般是数据目录下的auto.cnf。transaction_id事务序号从1开始递增。举个例子主库的UUID是3e11fa47-71ca-11e1-9e33-c80aa9429562它上面执行的第6个事务GTID就是3e11fa47-71ca-11e1-9e33-c80aa9429562:6注意transactions_id在主库上是连续递增的但事务如果被过滤比如从库用replicate_ignore_table过滤掉某些表从库的GTID序列可能有空洞。这很正常不影响一致性。2.2 三个关键状态变量你得刻在脑子里在MySQL里和GTID复制相关的状态信息主要存在几个系统变量和表里我按重要性排一下。变量/表作用理解要点gtid_executed当前实例上已经执行过的所有GTID集合查询方式SELECT GLOBAL.gtid_executedgtid_purged已经被purge掉的binlog里包含的GTID集合从库做备份恢复时会用到mysql.gtid_executed表持久化记录GTID防止内存中丢失8.0里此表为InnoDB表定期压缩performance_schema.replication_connection_status复制连接状态看从库接收到了哪些GTIDperformance_schema.replication_applier_status应用状态看从库应用到了哪个GTID我单独解释一下mysql.gtid_executed表。早期版本依赖binlog来记录GTID如果从库关了binlog重启后GTID信息就丢了。从MySQL 5.7.5开始每次事务提交时会把GTID写入这张表而且是和事务原子提交的保证GTID信息不丢失。8.0里这张表改成了InnoDB引擎还支持周期性压缩减少了表膨胀问题。2.3 从库同步一个事务的完整路径一个事务在GTID模式下的流转大致是这么个过程主库执行事务生成GTID记录到binlog里。从库的IO线程从主库拉取binlog解析出GTID写入从库的relay log。SQL线程读取relay log时先检查该GTID是否已经在从库的gtid_executed里。如果已经存在直接跳过该事务如果不存在就应用事务并把GTID记入gtid_executed。每个事务在从库应用完成后与主库的执行结果保持一致复制位点的推进以GTID为唯一依据。这套机制带来一个非常关键的收益从库天然具备防重放能力。哪怕你误操作让从库重复拉取了一段binlog它也能靠GTID判断出来哪些事务已经执行过自动跳过。这在老的位置模式下是不敢想的老模式一旦重复应用事务数据就乱了。3. 搭建前的关键配置这些参数踩不得理解了机制下面进入实战。先把环境说清楚我这边是两台CentOS 7.9虚拟机主库Master192.168.80.131MySQL 8.0.26从库Replica192.168.80.132MySQL 8.0.26安装方式用的是二进制tar包解压安装这里不赘述安装过程重点说配置。3.1 主库必须开启的参数编辑MySQL配置文件my.cnf在[mysqld]段下加以下配置# 服务器唯一标识主从必须不同 server-id 131 # 开启binlogGTID模式下必须开启 log_bin mysql-bin # 推荐的binlog格式务必使用ROW binlog_format ROW # GTID模式开关 gtid_mode ON # 强制GTID一致性 enforce_gtid_consistency ON # binlog的GTID信息记录方式8.0默认就是ON建议显式写出 binlog_gtid_simple_recovery ON # 从库需要记录自己的binlog方便后续级联或提升为主库 log_slave_updates ON3.2 每一个参数为什么这么配我先说server-id。整个复制拓扑里每个实例的server-id必须全局唯一。我见过有人图省事主从都用默认的1结果从库IO线程反复报错连复制都建立不了。建议直接拿IP最后一段来做标识好记又不冲突。再说gtid_mode ON。这个参数是GTID的总开关。MySQL 8.0里它支持OFF、OFF_PERMISSIVE、ON_PERMISSIVE、ON四种状态正常生产环境直接设ON就行。但要注意一点从非GTID模式切换过来时不能直接把OFF改成ON必须按OFF_PERMISSIVE→ON_PERMISSIVE→ON的步骤逐步切换不然会报错。这部分在第六节我会专门讲。enforce_gtid_consistency ON是什么意思GTID模式对某些语句有严格要求不允许在事务里同时更新InnoDB和MyISAM表因为MyISAM不支持事务无法保证原子性不允许CREATE TEMPORARY TABLE和DROP TEMPORARY TABLE在事务中使用不允许一条语句更新多个事务引擎表。这些限制本质上是保证每个事务都能被分配一个GTID并能安全回放。你在8.0里如果有历史遗留的MyISAM表开GTID后可能会遇到这个限制需要提前做表引擎迁移。binlog_gtid_simple_recovery ON这个参数控制崩溃恢复时扫描binlog的方式。设为ON时MySQL只扫描最旧和最新的两个binlog来构建GTID集合恢复速度快很多8.0默认开启不用动它。最后是log_slave_updates ON。这在从库上尤其重要。它表示从库应用主库的事务时也把自己的binlog记录下来。如果不开从库提升为主库后下游的从库会失去同步源。MySQL 8.0官方文档明确说GTID模式下建议开启。提示如果你的从库暂时没有下游节点log_slave_updates也要开。因为你不知道哪天它会转正真到要转正的时候才想起来改配置还得重启流程就拉长了。3.3 从库配置注意点从库的my.cnf和主库基本一样唯一不同就是server-idserver-id 132 log_bin mysql-bin binlog_format ROW gtid_mode ON enforce_gtid_consistency ON binlog_gtid_simple_recovery ON log_slave_updates ON直观起见主从配置对比如下配置项主库从库server-id131132log_binmysql-binmysql-binbinlog_formatROWROWgtid_modeONONenforce_gtid_consistencyONONlog_slave_updatesONON配置改完后主从两台都执行一遍systemctl restart mysqld再确认GTID相关变量已经生效SHOW VARIABLES LIKE gtid_mode; SHOW VARIABLES LIKE enforce_gtid_consistency; SHOW VARIABLES LIKE log_slave_updates;三条命令的输出都应该是ON有一个是OFF都说明配置没生效回去检查配置文件。4. 一主一从完整落地实录配置准备好了接下来就是创建复制用户、初始化数据、建立复制链路。整个过程我建议按顺序执行别跳步。4.1 在主库创建复制专用账号老规矩复制账号不能直接用root要创建一个最小权限的专用账号。MySQL 8.0的创建语法和5.7有一些差异不能在一条语句里同时指定加密规则和密码必须先创建再修改CREATE USER repl192.168.80.% IDENTIFIED BY Repl_123456; GRANT REPLICATION SLAVE ON *.* TO repl192.168.80.%; FLUSH PRIVILEGES;这里权限只用REPLICATION SLAVE就够了千万不要给这个账号分配其他权限从库只需要拉binlog的权限给多了反而有安全隐患。4.2 数据初始化的正确姿势如果主库是一台全新实例没有任何业务数据那可以跳过这步直接建立复制。但绝大多数情况是主库已经有存量数据了这时候必须保证从库的数据和主库完全一致否则复制链路一建立主从就会出现数据不一致。我推荐用mysqldump做逻辑备份简单直接适合数据量在几十GB以内的场景。在主库执行mysqldump -uroot -p \ --single-transaction \ --set-gtid-purgedON \ --all-databases \ --master-data2 \ master_dump.sql参数逐个解释--single-transaction在InnoDB引擎下通过开启一个可重复读的事务来保证备份一致性不加锁不会阻塞线上写入。--set-gtid-purgedON这是GTID模式下备份的关键参数。它会把当前主库的gtid_purged和gtid_executed集合写进备份文件的头部。从库恢复时只要执行这个文件就会自动把gtid_purged设置成主库的GTID集合从库就知道这些事务我已经有了不会再应用一遍。--master-data2记录备份时刻的binlog位置。GTID模式下这个信息不是必需的但写上无妨万一以后排查问题时能用上。--all-databases备份所有库。注意这个参数在8.0里还会备份mysql系统库的数据如果你想保留原实例上的用户账号这个选项很有用。把备份文件拷贝到从库scp master_dump.sql root192.168.80.132:/tmp/在从库上恢复mysql -uroot -p /tmp/master_dump.sql恢复过程中会自动建立主库的GTID边界执行完后可以查一下SHOW GLOBAL VARIABLES LIKE gtid_purged;输出里应该能看到主库的UUID列表。注意如果从库之前已经执行过一些事务并产生过gtid_executed直接恢复备份会报错The server has GTIDs purged that are not in the slaves gtid_executed。遇到这种情况先重置从库的GTID信息RESET MASTER;再执行恢复。我踩过这个坑提前帮你排掉了。4.3 建立复制链路数据恢复完成后在从库上执行CHANGE MASTER TO命令。GTID模式下的命令和传统模式有很大区别不再需要指定MASTER_LOG_FILE和MASTER_LOG_POS只需要指定主库地址、账号和GTID自动定位方式CHANGE MASTER TO MASTER_HOST192.168.80.131, MASTER_PORT3306, MASTER_USERrepl, MASTER_PASSWORDRepl_123456, MASTER_AUTO_POSITION1;MASTER_AUTO_POSITION1就是开启GTID自动定位。这里我还想提醒一下执行这条命令前从库必须已经通过备份恢复过数据或者确定从库当前GTID执行范围在主库的GTID范围内否则复制链路建立后IO线程会直接报错The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION 1, but the master has purged binary logs containing GTIDs that the slave requires。接着启动复制START SLAVE;在MySQL 8.0.22之后官方更推荐用START REPLICA替代START SLAVE但从兼容性考虑START SLAVE依然可用。我下面的查询还是用SHOW SLAVE STATUS因为它字段信息大家最熟。检查复制状态SHOW SLAVE STATUS\G重点关注输出中的这几个字段Slave_IO_Running: YesSlave_SQL_Running: YesRetrieved_Gtid_Set: 3e11fa47-71ca-11e1-9e33-c80aa9429562:1-100Executed_Gtid_Set: 3e11fa47-71ca-11e1-9e33-c80aa9429562:1-100如果IO和SQL线程都是YesRetrieved和Executed的GTID集合还一致说明主从复制已经正常跑起来了。4.4 验证主从真的在同步状态是Yes只代表复制链路通了不代表数据真的在同步。我最喜欢的验证方式是实际写一条数据看看从库能不能查到。在主库执行CREATE DATABASE demo; USE demo; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; INSERT INTO t_user(name) VALUES (张三),(李四),(王五);然后去从库查USE demo; SELECT * FROM t_user;能查到这三条记录复制链路就算真正打通了。5. 复制中断排查我碰过的三类高频故障GTID模式虽然省心但也不是绝对无灾无难。我总结了实际运维中最常碰到的三类故障连同完整的排查链路一起写出来。5.1 IO线程假死Last_IO_Errno 1236这个报错非常经典错误信息大概是Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION 1, but the master has purged binary logs containing GTIDs that the slave requires.根因从库需要的GTID事务在主库的binlog里已经找不到了。通常是因为主库执行了PURGE BINARY LOGS或者binlog过期时间binlog_expire_logs_seconds设置得太短日志被清理掉了。排查链路第一步在从库看Executed_Gtid_SetSHOW SLAVE STATUS\G确认从库执行到的GTID范围。第二步在主库查看gtid_purgedSHOW GLOBAL VARIABLES LIKE gtid_purged;如果主库的gtid_purged集合包含了从库需要的GTID那就实锤了。处理方案没有完美方案只能重建从库。最直接的做法是用前面第4.2节的方法重新备份主库数据、恢复到从库、重建复制。这也是我不建议在生产上把binlog过期时间设太短的另一个原因一旦从库宕机超过binlog保留窗口重建整个从库的成本远比想象高。5.2 SQL线程罢工主键冲突导致复制中断这种场景常见于主从库都接受写入的情况比如主库写了一条id100的数据从库也被人为写入过id100的数据复制到这条记录时就报主键冲突SQL线程自动停止。排查链路SHOW SLAVE STATUS\G看到Last_SQL_Errno: 1062Last_SQL_Error里写着Duplicate entry 100 for key PRIMARY。处理方案如果确定从库这条冲突数据是垃圾数据可以直接跳过这个事务。GTID模式下跳过事务不能再用传统的SET GLOBAL SQL_SLAVE_SKIP_COUNTER 1了5.7还能凑合用8.0里已经废了正确的做法是手动构造一个空事务把有问题的GTID吃掉STOP SLAVE; SHOW SLAVE STATUS\G -- 记录 Executed_Gtid_Set 和 Retrieved_Gtid_Set 的差异找出冲突的事务GTID后在主库上执行从库没有主库的server_uuid直接执行会报错所以要在主库构造空事务-- 在主库上执行 BEGIN; COMMIT;但只执行这个空事务并不会生成和冲突事务一样的GTID。更准确的做法是把从库的GTID透传过去手动注入-- 从库上停止复制后 SET GTID_NEXT3e11fa47-71ca-11e1-9e33-c80aa9429562:101; BEGIN; COMMIT; SET GTID_NEXTAUTOMATIC; START SLAVE;这段命令的作用是手动告知从库这个GTID对应的事务已经执行过了实际上没执行任何数据变更然后再启动复制。注意我已经指定了具体的GTID所以不需要在主库操作。这里我再强调一次跳过事务是一种止损操作只适合确认冲突数据不影响业务的情况。如果冲突的是一条重要的业务数据你应该先把从库的数据改成和主库一致再启动复制而不是粗暴跳过。5.3 从库状态正常但数据就是追不上有一种隐蔽的故障SHOW SLAVE STATUS里两个线程都是YesSeconds_Behind_Master数值却在增长从库永远追不上主库。排查链路这种通常是某个大事务导致的。比如主库跑了一个几百万行的UPDATE生成了一大批binlog事件从库重放这些事件的速度远低于主库生成的速度。你可以用SHOW PROCESSLIST查看从库当前的SQL线程在执行的语句SHOW PROCESSLIST;如果看到一条长UPDATE或长INSERT一直处于System lock或Updating状态基本就锁定问题了。处理方案大事务对主从延迟的影响很难彻底消除只能缓解。常见做法有几个把大事务拆成小批次执行比如UPDATE语句加LIMIT循环更新或者按主键范围分片提交。优化从库的写入性能比如调整innodb_buffer_pool_size、innodb_flush_log_at_trx_commit等参数仅在从库上调主库保持默认的更安全的刷盘策略。从库使用更快的磁盘和文件系统比如NVMe SSD配合fstrim定期回收空间。检查从库是否开启了read_only保证没有其他应用在往从库写入抢夺IO资源。提示Seconds_Behind_Master在某些场景下并不靠谱。比如从库SQL线程没有在跑任何事务时它可能显示为0网络中断恢复后它可能显示为很大的值。更精确的判断方法是对比Retrieved_Gtid_Set和Executed_Gtid_Set的差异以及主库的gtid_executed和从库的Executed_Gtid_Set之间的差距。6. 切换、扩容与备份恢复时的GTID注意点主从环境搭好了之后你还会面临主从切换、扩容从库、备份恢复这些高频操作。GTID模式下这些操作和老模式不完全一样我挑重点讲。6.1 从库提升为主库的正确动作假设主库硬件故障需要把从库提升为主库。GTID模式下步骤很简单在从库执行STOP SLAVE;停止复制。执行RESET SLAVE ALL;清除复制信息。如果有下游从库需要把它们的复制源指向这个新主库并确认MASTER_AUTO_POSITION1。如果没有下游从库把应用流量切到新主库即可。新主库在切换前需要确保read_onlyOFF一般从库配置了read_only的话并且已经执行过所有主库同步过来的事务。判断依据就是Executed_Gtid_Set和原主库的gtid_executed是否一致。6.2 非GTID模式平滑切换到GTID模式如果你手头有一套传统位置复制的环境想平滑升级到GTID模式不能直接改gtid_modeON然后重启。必须按顺序做把主从库都设置gtid_modeOFF_PERMISSIVE重启实例。把主从库都设置gtid_modeON_PERMISSIVE重启实例。等待一段时间让主库产生的新事务全部带上GTID。可以通过SHOW VARIABLES LIKE gtid_mode确认。确认没有匿名事务SHOW STATUS LIKE ONGOING_ANONYMOUS_TRANSACTION_COUNT值为0后把gtid_modeON重启实例。把从库的复制源改成GTID自动定位CHANGE MASTER TO MASTER_AUTO_POSITION1。这个流程每一步重启都会产生一次主从中断窗口所以一定要规划好维护时间。6.3 用备份文件搭建新从库的GTID陷阱前面4.2节我提到了--set-gtid-purgedON。我再补充一个容易翻车的细节如果你用mydumper或xtrabackup这类第三方工具做备份必须确认它们对GTID的支持情况。我用xtrabackup遇到过一个问题备份出来的文件里xtrabackup_binlog_info文件记录的是普通位点而xtrabackup_gtid_executed文件记录的是GTID集合。如果你在从库上恢复xtrabackup备份后直接CHANGE MASTER TO MASTER_AUTO_POSITION1可能会因为gtid_purged没有正确设置而导致复制报错。解决办法是在从库恢复完备份后先手动设置gtid_purgedSET GLOBAL gtid_purged3e11fa47-71ca-11e1-9e33-c80aa9429562:1-500;执行前要确保从库的gtid_executed是空的否则会报错。这也是为什么官方日志备份加--set-gtid-purgedON一步到位的原因——它会在恢复时自动完成这个设置。7. 我最后的几句实在话GTID主从搭起来之后我个人的习惯是把日常巡检脚本里加上几条GTID检查每个小时跑一下所有从库的SHOW SLAVE STATUS重点看Executed_Gtid_Set和主库的gtid_executed差值差值超过阈值就告警。这套检查在纯位点模式下很难做到因为每个节点维护的位置信息都是独立的无法直接对比但在GTID模式下所有节点的GTID集合是同一套坐标系跨节点对比非常直观。最后再分享一个小技巧在从库上执行SHOW SLAVE STATUS输出的Executed_Gtid_Set通常是一长串UUID看起来很乱。你可以在主库上执行SHOW MASTER STATUS直接比对两个结果里的GTID集合是否一致不用肉眼看串。如果两边GTID一致说明主从已经追平。这个操作我在每次切换前必做稳妥得很。
返回列表