☰
MySQL主从复制实战:binlog、GTID、延迟排查与半同步切换
2026/10/31 8:25:03 网站建设 项目流程

MySQL主从复制这东西,属于那种"平时没人想起,出事全公司盯着"的基础设施。我印象最深的一次是某个凌晨两点,业务侧反馈某类查询返回了过期数据,排查下来主库早就提交成功了,从库回放卡在一条大表的 UPDATE 上,Seconds_Behind_Master还老老实实显示 0。那次之后我重新把复制的链路、参数和坑位完整梳理了一遍。这篇就把 MySQL 主从复制从原理到落地拆开讲:三个线程分别干什么、binlog 格式怎么选、异步和 GTID 怎么搭、延迟和一致性问题怎么查、半同步和切换要注意什么。适合已经会写 SQL 但没亲手搭过复制链路的同学,也适合维护着一主两从、想搞清楚哪些参数不能随便动的人。

1. 复制链路里其实只跑着三个线程,但时间线比想象中长

很多人对复制的理解停在"主库写 binlog、从库读 binlog 回放"这一句话上。这句话没错,但它省掉了所有会出问题的地方:binlog 什么时候真正落盘、从库什么时候拉取、relay log 什么时候被消费、事务在从库什么时候可见。这四个时间点之间的距离,就是延迟、丢数据和各种诡异现象的来源。

1.1 主库的 dump 线程与事务提交的真实落盘时机

主库侧负责复制的是 binlog dump 线程。从库 IO 线程连上来,发一个"我要从某个位置开始"的请求,dump 线程就把 binlog 里对应位置之后的 event 读出来推过去。这个线程是被动的,只有从库连着才工作,不连就不推。所以从库断了之后,主库的 binlog 会一直往前滚,滚到超过保留期被 purge 掉,从库再回来就接不上了——这是最经典的一类故障。

真正决定"主库说提交成功了,这条数据到底安不安全"的,是另外两个参数。sync_binlog控制 binlog 从内存缓冲刷到磁盘的频率,设成 1 表示每次事务提交都刷一次磁盘;innodb_flush_log_at_trx_commit控制 redo log 的刷盘策略,同样设成 1 表示每次提交都落盘。这两个都设 1,才是常说的"双一"配置。

为什么不设 1 会出问题?假设sync_binlog=0,事务提交后 binlog 还留在操作系统缓存里,主库此时异常重启,这条 binlog 可能就没了,但从库那边可能已经通过 dump 线程把它拉走了并且回放完了。主库恢复后没有这条记录,从库有,这就是主从不一致。所以任何对数据可靠性有要求的场景,这两个参数都不能省。

注意:sync_binlog=1对写入性能是有实打实影响的,尤其是在高并发小事务场景下。我见过不少环境为了压测数字好看把它调大,上线后主从数据出现差异,排查花的时间远超省下的那点 QPS。

1.2 从库的两段式拉取:IO 线程写 relay log,SQL 线程负责回放

从库侧是两个线程配合。IO 线程负责和主库的 dump 线程通信,把收到的 event 顺序写进本地的 relay log,同时更新mysql.slave_master_info表里记录的主库位点。SQL 线程则是另一个独立角色,它读 relay log,把里面的 event 在自己的存储引擎上重放一遍,然后更新mysql.slave_relay_log_info里的位点。

这两个线程完全异步,这也是延迟产生的结构性原因:IO 线程拉得再快,SQL 线程跟不上,relay log 就在磁盘上越堆越多。反过来,SQL 线程很快但网络或主库压力导致 IO 线程拉得慢,relay log 就很短。所以看延迟不能只看一个数字,得分别看 IO 和 SQL 两个线程的状态。

在 8.0 里位点信息默认存在两张 InnoDB 表里,而不是文件。这个改动很关键:用表存位点意味着位点更新和事务回放可以在同一个事务里完成,从库崩溃恢复时位点和数据是一致的。早期用文件存位点的版本,崩溃后经常出现"位点已经推进了但事务没回放完"或者反过来,非常难处理。

1.3 一条事务从主库提交到从库可见,中间隔着多少环节

把链路串起来看,一条事务在从库可见之前要经过这些环节:

环节发生位置耗时影响因素
主库 binlog 落盘主库磁盘 IO、sync_binlog
dump 线程推送主库到从库网络网络带宽、RTT、binlog 体积
IO 线程写 relay log从库从库磁盘、relay log 刷盘策略
SQL 线程回放从库从库 CPU、锁竞争、并行度、单事务大小

这四段里任何一段变慢,最终体现出来的都是"应用读到了旧数据"。而且这四段的延迟特征完全不同:网络问题表现为 IO 线程状态异常、relay log 不增长;回放问题表现为 relay log 持续增长、SQL 线程一直 running。定位方向完全不一样,先分清楚是哪一段,比一上来就调参数有用得多。

2. binlog 格式这道选择题,选错了后面全在还债

binlog_format有三个取值:STATEMENT、ROW、MIXED。这个参数在 5.7 之后默认就是 ROW,8.0 更是固化了这个方向。但我在不少存量环境里还看到 STATEMENT,问起来通常是"当年为了省空间"或者"一直没改过"。

2.1 STATEMENT 在真实业务里为什么越来越站不住脚

STATEMENT 记录的是原始 SQL 语句。听起来很省空间,但它有一个致命前提:同一条 SQL 在主库和从库执行必须产生完全相同的效果。这个前提在现实里经常不成立。

举几个典型情况。带LIMIT不带ORDER BY的更新,主从两侧的扫描顺序可能不同,更新的行就不一样。用到NOW()、RAND()、UUID()这类非确定性函数,两侧取值不同。还有自增列,在并发插入场景下主从分配到的自增值可能错位。这些都不是理论问题,我在生产环境里见过用RAND()做随机排序分页的查询,配上 STATEMENT 格式,从库数据直接错乱。

还有一个更隐蔽的:语句里带了用户变量或者临时表操作,SQL 线程重放时的上下文和主库不一样,结果同样无法保证。

提示:如果接管了一个还在用 STATEMENT 格式的老库,不要直接改参数了事。先跑一轮全量一致性校验,确认当前主从数据是对齐的,否则切换格式后差异会被固化下来,后面更难处理。

2.2 ROW 模式的空间与 IO 代价,以及怎么把它压回去

ROW 格式记录的是每一行数据变更前后的镜像,好处是确定性极强,SQL 线程回放时不依赖任何环境因素,也不依赖 SQL 的具体写法。代价是 binlog 体积可能暴涨,尤其是批量更新场景:一条UPDATE t SET status=0 WHERE create_time < '2024-01-01'影响十万行,STATEMENT 只记一条语句,ROW 要记十万行的前后镜像。

体积上去了,直接带来三个连锁反应:磁盘写入量增加、主从网络传输压力增加、从库 relay log 占用增加。这三点叠加起来,延迟自然就上来了。

压回去的思路有这么几个。一是控制单次事务影响的行数,批量更新拆成小批,比如每批一千行,中间留一点间隔。这不光减小 binlog,也减少主库的锁持有时间和从库的回放压力。二是调整 binlog 的过期时间,8.0 里用binlog_expire_logs_seconds,默认三十天,很多业务根本不需要留这么久,按从库最长可能断开的时间来估算就行。三是合理设置max_binlog_size,默认 1G,大事务会突破这个限制产生超大文件,拆分事务能顺带解决。

2.3 binlog_row_image 与最小化写入

ROW 格式下还有一个参数值得单独说:binlog_row_image,取值 FULL、MINIMAL、NOBLOB。默认是 FULL,记录变更行的所有列,无论这一列有没有变。设成 MINIMAL 后只记录主键和被修改的列,binlog 体积能明显下降。

但 MINIMAL 有个前提:从库上必须能通过主键唯一定位到那一行。表如果没有主键或者唯一键,MINIMAL 模式下回放会出问题,可能导致从库扫描全表甚至报错。所以这个参数不能脱离表结构单独讨论。

我在实际使用中的做法是:先把无主键表清理掉(8.0.13 之后可以用sql_require_primary_key=ON从源头拦住新建无主键表),确认所有表都有主键,再考虑开 MINIMAL。反过来,如果环境里还留着一批历史无主键表,就老老实实保持 FULL,稳定压倒一切。

3. 从零搭一套异步复制,几行配置背后躲着的坑

配置本身不复杂,复杂的是顺序、权限和一致性快照。下面这套流程我在多个环境里跑过,按这个顺序基本不会卡住。

3.1 server_id、log_bin、binlog_format 这几个必选项

主库的my.cnf需要确认这几项:

[mysqld] server_id = 101 log_bin = /var/lib/mysql/binlog binlog_format = ROW binlog_row_image = FULL sync_binlog = 1 innodb_flush_log_at_trx_commit = 1 binlog_expire_logs_seconds = 604800 max_binlog_size = 512M

从库对应配置:

[mysqld] server_id = 102 relay_log = /var/lib/mysql/relaylog relay_log_recovery = ON read_only = ON super_read_only = ON log_replica_updates = ON

server_id必须全局唯一且非零,这是硬性要求。我遇到过一次克隆虚拟机导致两台机器server_id相同,现象是复制刚启动就断,报错信息里明确提到主从 server id 一致。这个坑很蠢但很常见,做镜像或模板的时候特别容易忘。

relay_log_recovery = ON也是必须的。它让从库启动时丢弃未处理完的 relay log,重新从位点拉取,避免 relay log 损坏导致回放中断。8.0 里这个参数默认就是 ON,但老环境升级上来可能是 OFF。

super_read_only = ON比read_only = ON更彻底。read_only对拥有 SUPER 权限的账号无效,也就是说运维账号照样能往里写,误操作防不住。super_read_only会把这类账号也挡住。

3.2 主库建复制账号,权限粒度别图省事

CREATE USER 'repl_user'@'10.0.0.%' IDENTIFIED BY 'strong_password_here'; GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'10.0.0.%'; FLUSH PRIVILEGES;

只给REPLICATION SLAVE权限就够了,不要图省事给ALL PRIVILEGES或者SUPER。复制账号一旦被盗,能做的事情越少越好。另外主机段尽量收窄,不要写%。

3.3 用 mysqldump 做一致性快照时的两个关键参数

从库初始化需要一份主库的一致性快照,同时要知道这份快照对应的 binlog 位点。mysqldump能同时完成这两件事:

mysqldump -h 10.0.0.11 -u root -p \ --single-transaction \ --source-data=2 \ --set-gtid-purged=OFF \ --databases app_db \ > /tmp/app_db.sql

--single-transaction让整个导出过程在一个可重复读事务里完成,保证快照一致,同时不锁表(前提是表都用 InnoDB)。--source-data=2(老版本叫--master-data=2)会把当时的 binlog 位点以注释形式写进导出文件,后面配复制时直接从文件里拿就行。

--set-gtid-purged这个参数容易踩坑。如果目标环境要用 GTID 复制,设成 ON 或者注释掉,导出文件里会带上SET @@GLOBAL.GTID_PURGED语句;如果目标环境不用 GTID,必须设成 OFF,否则导入时会报 GTID 相关的错。

导入完成后,从导出文件的注释里找到位点:

grep -m1 "CHANGE REPLICATION SOURCE" /tmp/app_db.sql

3.4 CHANGE REPLICATION SOURCE 与启动验证

在从库上执行(8.0.23 之后的写法,之前的版本用CHANGE MASTER TO,参数名对应为MASTER_HOST等):

CHANGE REPLICATION SOURCE TO SOURCE_HOST = '10.0.0.11', SOURCE_PORT = 3306, SOURCE_USER = 'repl_user', SOURCE_PASSWORD = 'strong_password_here', SOURCE_LOG_FILE = 'binlog.000042', SOURCE_LOG_POS = 156; START REPLICA;

启动后重点看三个地方。SHOW REPLICA STATUS\G里Replica_IO_Running和Replica_SQL_Running都应该是Yes;Last_IO_Error和Last_SQL_Error应该是空的;Seconds_Behind_Master一开始会比较大,等追平后归零。

如果 IO 线程起不来,八成是网络、账号权限或者位点不对。如果 SQL 线程起不来,看Last_SQL_Error,常见的是主从表结构不一致、从库有写入了数据导致主键冲突。

4. GTID 模式,新环境真的不该再抱着 file+position 不放

binlog file + position 这套机制能用,但它有个根本问题:位点是环境相关的。换一台机器、重做一次快照、切换一次主库,位点就变了,所有下游都得跟着改。GTID 就是为了解决这个问题。

4.1 GTID 的组成与自动定位到底省了什么

GTID 的格式是server_uuid:transaction_id,比如3e11fa47-71ca-11e1-9e33-c80aa9429562:23。每个事务在主库提交时被分配一个全局唯一编号,从库记录自己已经执行过的 GTID 集合。配置复制时只要告诉从库"从你还没有的那部分开始",不需要关心文件位置。

这个改变带来的直接好处是切换变简单了。主库挂掉,把某个从库提升为主,其他从库重新指向新主库,只要SOURCE_AUTO_POSITION=1,它们会自动算出自己缺哪些事务。用位点的方案下,这一步要手工算每个从库的位点,从库一多就是噩梦。

另外 GTID 让"某个事务是否已经在某台机器上执行过"变成一个可以查询的问题。SHOW REPLICA STATUS里的Executed_Gtid_Set和Retrieved_Gtid_Set能直接告诉你缺口在哪,排查时省掉很多猜测。

4.2 从传统模式切到 GTID 的四步状态机

GTID 不能直接从 OFF 跳到 ON,必须走中间状态,因为集群里可能存在还没处理完的匿名事务。标准顺序是这样:

-- 第一步:先开一致性检查,观察一段时间日志 SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = WARN; -- 观察无异常后 SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = ON; -- 第二步:进入两种中间态 SET @@GLOBAL.GTID_MODE = OFF_PERMISSIVE; SET @@GLOBAL.GTID_MODE = ON_PERMISSIVE; -- 第三步:等待匿名事务清零 SHOW STATUS LIKE 'ONGOING_ANONYMOUS_TRANSACTION_COUNT'; -- 必须等到 Value 为 0 -- 第四步:正式开启 SET @@GLOBAL.GTID_MODE = ON;

每一步都要同步写到配置文件里,否则重启后会回退。ONGOING_ANONYMOUS_TRANSACTION_COUNT是这一整套流程里唯一需要等待的地方,如果集群里有持续写入,可能要等一会儿。这个值不为 0 就切最后一步,会留下没有 GTID 的事务,后面很麻烦。

提示:切换过程中不要同时做表结构变更,也不要在切换中间态时重启实例。中间态是设计成可回退的,但回退和前进交错执行容易出问题。

4.3 GTID 模式下跳过错误的正确姿势

这是我在实际使用中踩得最深的一个坑。传统模式下复制出错,很多人习惯用SET GLOBAL sql_slave_skip_counter = 1; START SLAVE;跳过。GTID 模式下这个参数直接不可用,设了也不生效,会报错提示。

正确的做法是注入一个空事务,让从库"认为"这个 GTID 已经执行过:

STOP REPLICA; SET GTID_NEXT = '3e11fa47-71ca-11e1-9e33-c80aa9429562:23'; BEGIN; COMMIT; SET GTID_NEXT = 'AUTOMATIC'; START REPLICA;

这里的 GTID 要填报错信息里Last_SQL_Error提到的那个。原理是手工执行一个空事务,把这个编号写进从库的gtid_executed集合,复制再继续时就会跳过它。

当然,跳过的前提是你确认这个事务确实可以丢。跳过之前务必把对应 binlog 解析出来看清楚内容,用mysqlbinlog加上 GTID 过滤参数定位到具体事务。直接跳等于承认数据不一致,后面得靠校验和补数据来兜底。

5. 延迟与不一致,从 Seconds_Behind_Master 骗人那一刻开始排查

延迟排查最怕的就是看一个数字就下结论。Seconds_Behind_Master这个值在多种场景下会给出误导性结果,必须结合 relay log 的增长情况和两个线程的状态一起看。

5.1 Seconds_Behind_Master 为什么不可信,替代方案是什么

这个值的计算方式是"SQL 线程当前回放的事件时间戳"减去"IO 线程最近一次收到的事件时间戳"。听起来合理,但有几个明显的失效场景。

主库长时间没有写入时,两个时间戳都停在很久以前,差值接近 0,看起来毫无延迟,但实际上从库可能因为某个大事务卡住了,relay log 里堆着一堆没回放的内容。反过来,从库刚启动时 SQL 线程还没开始跑,这个值可能直接是 NULL。

更靠谱的做法是用心跳表。原理是在主库上每隔固定时间(比如一秒)更新一行记录的时间戳,从库上查这行记录,用当前时间减去记录里时间戳,得到的才是真实延迟:

-- 主库定时执行 UPDATE heartbeat SET ts = NOW() WHERE id = 1; -- 从库查询 SELECT TIMESTAMPDIFF(SECOND, ts, NOW()) AS real_delay FROM heartbeat WHERE id = 1;

这个方案的好处是直观、准确,不依赖内部实现细节。社区里有现成的工具封装了这个逻辑,也可以自己写个脚本定时跑。我个人更倾向自己写,逻辑简单,出问题好排查。

还有一个辅助判断:看 relay log 文件数量和总大小。如果持续增长,说明 SQL 线程跟不上,问题在回放侧;如果基本不增长但延迟报警,问题多半在 IO 侧或者主库根本没写入。

5.2 无主键表与大事务,两个最高频的延迟源头

无主键表在 ROW 模式下是灾难。主库执行一条带条件的 UPDATE,binlog 里记录的是每行变更的前后镜像,从库回放时需要通过主键定位到对应行。表没有主键,从库只能全表扫描,一条影响一万行的更新,从库可能要扫一万次全表。表越大,延迟越夸张,而且会持续累积。

判断方法很简单,查一下有没有无主键表:

SELECT t.table_schema, t.table_name FROM information_schema.tables t LEFT JOIN information_schema.table_constraints c ON c.table_schema = t.table_schema AND c.table_name = t.table_name AND c.constraint_type = 'PRIMARY KEY' WHERE t.table_schema NOT IN ('mysql','sys','information_schema','performance_schema') AND t.table_type = 'BASE TABLE' AND c.constraint_name IS NULL;

解决就是加主键,没有业务主键就加自增列。这件事没有捷径。

大事务是另一个源头。一个事务更新几十万行,从库回放时这个事务要么全做完要么全不做,中间过程对其他会话的锁影响也大。表现是延迟曲线呈现明显的尖峰,尖峰出现的时间和主库的批量任务时间对得上。

处理方式是把大事务拆小。批量更新按主键范围分批,每批控制在几千行以内,批与批之间短暂停顿。这不只是优化复制延迟,对主库的锁持有时间、undo 空间占用、甚至备份的稳定性都有好处。

5.3 并行回放的参数组合与生效条件

单线程回放是复制延迟的结构性瓶颈:主库并发写入,从库排队一个个回放,写入压力一大必然追不上。8.0 的并行回放能一定程度缓解,但有生效条件。

[mysqld] replica_parallel_workers = 8 replica_parallel_type = LOGICAL_CLOCK replica_preserve_commit_order = ON binlog_transaction_dependency_tracking = WRITESET

replica_parallel_workers设成 8 意味着最多 8 个工作线程并行回放。LOGICAL_CLOCK模式允许在主库上同时提交的事务在从库上并行执行,这是并行的基础。replica_preserve_commit_order保证从库上的提交顺序和主库一致,开了之后并行才安全——关掉它会出现从库读到中间状态的问题,一般不建议关。

binlog_transaction_dependency_tracking控制主库怎么标记事务之间的依赖关系,WRITESET是基于行级别的冲突检测,比COMMIT_ORDER并行度更高。8.0 里默认就是WRITESET。

有个关键前提容易忽略:并行回放的效果取决于主库上事务本身的并发度。如果业务是单线程串行写入,主库事务之间都有依赖,从库再并行也没用,还是得排队。所以不要指望调大replica_parallel_workers能解决所有延迟,先看清楚写入模式。

5.4 一致性校验的落地方式

延迟解决了不代表数据就是一致的。跳过的错误、切换时的边界情况、历史上的误操作,都可能留下不一致。定期校验是必要的。

CHECKSUM TABLE只能看出两张表整体是否一致,定位不到具体行,而且对大表不友好。更实用的方式是分块校验:按主键范围切分,每块计算一个摘要值,对比主从两侧。社区里有现成的工具做了这件事,会自己在从库上执行查询做比对,输出差异行并生成修复语句。

校验的节奏看业务容忍度。核心表可以每天跑一次,非核心表每周一次。校验本身有资源消耗,放在低峰期执行,并且限制并发和每次扫描的量,避免校验把从库压垮。

6. 半同步与切换,先把可用性问题算清楚再动手

异步复制的语义是"主库提交成功即返回客户端",从库有没有收到完全不保证。主库故障时,还没传过去的 binlog 就丢了。半同步复制是为了缩小这个窗口。

6.1 AFTER_SYNC 与 AFTER_COMMIT 的差别

半同步的核心是主库提交后等待至少 N 个从库确认收到 binlog,再返回客户端。这里面有个关键参数rpl_semi_sync_master_wait_point,取值AFTER_COMMIT和AFTER_SYNC。

AFTER_COMMIT是早期实现:主库先提交事务、写 binlog,然后等从库确认。这中间有个窗口,事务在主库已经提交、对其他会话可见了,但从库还没收到。如果此时主库崩溃,客户端可能已经读到了这个事务的结果,切换后这个事务却不存在。

AFTER_SYNC则是先等从库确认收到 binlog,再在主库上提交。这样事务对客户端可见的时候,从库一定已经拿到了 binlog。5.7 之后默认用AFTER_SYNC,这是更合理的语义。

配置示例:

-- 主库 INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so'; SET GLOBAL rpl_semi_sync_master_enabled = ON; SET GLOBAL rpl_semi_sync_master_timeout = 1000; SET GLOBAL rpl_semi_sync_master_wait_for_slave_count = 1; -- 从库 INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so'; SET GLOBAL rpl_semi_sync_slave_enabled = ON;

8.0.26 之后插件名和变量名改成了 source/replica 的说法,比如rpl_semi_sync_source_enabled,但旧名字一般还保留着兼容。

6.2 半同步退化成异步的静默风险

rpl_semi_sync_master_timeout设的是等待确认的超时时间,单位毫秒,默认一万毫秒。超时后主库不会报错,而是自动退化成异步复制。这个设计是为了可用性——从库全挂了也不能让主库写不进去。

问题在于这个退化是静默的。退化之后主库照常写入,从库照常在追,看起来一切正常,但语义已经从半同步变成了异步。如果没人监控这个状态,等下次主库真出问题时才发现数据丢了。

监控指标是Rpl_semi_sync_master_status,值为 OFF 就说明已经退化。这个指标要放进常规监控,退化了要告警。同时Rpl_semi_sync_master_no_tx这个计数器记录了多少次事务是没等到确认就提交的,也是重要参考。

注意:超时时间设得太短会导致频繁退化,设得太长会导致主库在从库异常时写入卡顿。一千毫秒是个常见的折中值,但具体要看网络 RTT 和业务能接受的写入延迟。

6.3 切换前的检查清单

主从切换是这类架构里风险最高的操作。我给自己定了几个必查项,每次切换前过一遍。

第一,确认新主库的数据是最全的。对比各从库的Executed_Gtid_Set,选包含事务最多的那个。如果有多个从库各自比别的从库多了不同事务,说明发生过复杂情况,需要先把缺的事务补齐再说。

第二,确认所有从库的 SQL 线程已经追平或接近追平。带着延迟切换,丢失的数据量会更大。

第三,确认应用的连接串能快速改指向。有没有配置中心、DNS 缓存多久、连接池会不会一直持有旧连接,这些都得提前确认。我见过切换完成后应用还在连老主库十几分钟的情况,原因是连接池没做失效处理。

第四,确认老主库已经彻底停止写入。切换后如果老主库恢复并且继续接受写入,就会出现双写,处理起来极其麻烦。可以在切换时直接把老主库设成super_read_only,或者干脆停掉。

第五,准备好回滚路径。切换失败要能切回去,这意味着老主库的 binlog 要完整保留,切换期间没有做过不可逆的操作。

7. 一些我在实际环境里反复用到的判断方法

复制链路出问题时,大部分人的第一反应是翻参数文档。我的经验是先花三十秒做一次分诊,能省掉大量试错。

看SHOW REPLICA STATUS里两个线程的状态,如果 IO 线程是Connecting,问题在连接层——网络不通、账号密码错、主库挂了、防火墙拦了。如果 IO 线程是No,看Last_IO_Error,里面通常写得很清楚。如果 IO 正常、SQL 线程是No,看Last_SQL_Error,这类错误一般分两种:数据冲突(主键重复、行不存在)和结构问题(表不存在、列不匹配)。数据冲突要用校验工具定位差异行后手工修复,结构问题先同步表结构再重启复制。

如果两个线程都是Yes但延迟在涨,看 relay log 增长速度和从库的负载。从库上跑着备份、跑着大查询、或者磁盘 IO 打满,都会拖慢回放。这种情况优先解决资源争抢,调参数是次要的。

最后分享两个小技巧。一是给Relay_Log_Space加监控,这个值持续增长就是延迟的早期信号,比延迟数值本身更灵敏。二是给Retrieved_Gtid_Set和Executed_Gtid_Set的差值做监控,能精确算出还差多少个事务没回放,比秒数更直观。这两个指标接进监控系统之后,很多问题在业务感知之前就能发现。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询