MySQL主从延迟突然拉大的排查路径和处理方法

描述

 

线上读写分离环境里,主库流量没有明显变化,某个只读实例的延迟却突然从几秒变成几分钟。此时最危险的做法是直接重启复制线程,或看到 Seconds_Behind_Source 很大就跳过当前事务;这可能掩盖真正的错误和数据缺口。

十分钟初筛要回答三件事:延迟在接收线程还是 SQL 应用线程;是单个事务卡住还是副本吞吐不足;当前应等待、降读、隔离还是升级。

本文以 MySQL 8.0、GTID、异步复制和多线程副本为例,使用 source/replica 表述主库和副本。8.0.23 及以后优先使用 SHOW REPLICA STATUS,较早版本可能使用 SHOW SLAVE STATUS,执行前用 SELECT VERSION() 确认。字段以实际版本输出为准,所有 <变量名> 都是需替换的占位符。

先记住一个原则:Seconds_Behind_Source 不是唯一证据

Seconds_Behind_Source 是基于线程执行时间和源库事件时间的估算,复制未运行、刚启动、时间不同步、并行 worker 不均衡或源库没有新事务时都可能为 NULL 或失真。真正可靠的判断要结合完整复制状态、GTID 集合、worker 错误、源库写入速率、副本磁盘与锁等待。

一、十分钟排查路径

时间 目标 必须留下的证据
0~1 分钟 确认版本、连接和状态 复制状态完整输出
1~3 分钟 区分 IO 线程和 SQL 线程 线程状态、错误字段、GTID 集合
3~5 分钟 查是否有单个 worker 或事务卡住 worker 错误、当前事务、锁
5~7 分钟 看源库写入是否突增 binlog、事务、连接和提交状态
7~9 分钟 看副本是否被读流量或磁盘拖慢 processlist、InnoDB、iostat
9~10 分钟 选择低风险动作 等待、限流、隔离、修复或升级

这条路径适合“延迟突然拉大”的初筛。数据一致性风险、复制中断、主库故障切换和跳过事务属于更高等级事件,不能只依靠十分钟快照完成决策。

二、0~1 分钟:确认你查的是正确实例

1. 先确认客户端和服务器版本

命令行客户端的版本不一定等于服务器版本,SHOW 语句按服务器版本执行。先同时记录二者:


			   bash#!/usr/bin/env bash set -euo pipefail MYSQL_BIN="" MYSQL_HOST="<副本地址>" MYSQL_PORT="<副本端口>" MYSQL_USER="<只读排障用户>" "$MYSQL_BIN" --version "$MYSQL_BIN"    --host="$MYSQL_HOST"    --port="$MYSQL_PORT"    --user="$MYSQL_USER"    --password    --connect-timeout=5    --batch --raw --skip-column-names    -e 'SELECT VERSION(), @@hostname, @@port, @@server_uuid, @@read_only, @@super_read_only;' 

排障账号应通过安全方式输入密码,不要把真实密码写进命令行、脚本或聊天记录。若连接超时,先查网络、端口和账号权限,不能把连不上副本直接当成复制 SQL 线程延迟。

2. 用一条 SQL 保存运行环境

在副本上执行下面的查询,记录 GTID、复制线程、并行度、日志格式和只读状态:


			   sqlSELECT   VERSION() AS mysql_version,   @@hostname AS hostname,   @@server_uuid AS server_uuid,   @@global.gtid_mode AS gtid_mode,   @@global.enforce_gtid_consistency AS enforce_gtid_consistency,   @@global.binlog_format AS binlog_format,   @@global.log_replica_updates AS log_replica_updates,   @@global.replica_parallel_workers AS replica_parallel_workers,   @@global.replica_parallel_type AS replica_parallel_type,   @@global.read_only AS read_only,   @@global.super_read_only AS super_read_only; 

MySQL 8.0 的变量名在小版本间存在更名,例如 log_slave_updates 与 log_replica_updates。若某个变量不存在,先用 SHOW VARIABLES LIKE ‘%replica%’; 和 SHOW VARIABLES LIKE ‘%slave%’; 确认当前版本,而不是直接修改配置。

三、1~3 分钟:先把延迟归类

1. 读取完整复制状态

副本上优先执行 SHOW REPLICA STATUS。不要只复制 Seconds_Behind_Source 一列,因为真正决定方向的是 IO、SQL、错误和位置字段的组合。


			   sqlSHOW REPLICA STATUSG 

重点记录以下字段:

  • Replica_IO_Running、Replica_SQL_Running、Replica_SQL_Running_State。
  • Seconds_Behind_Source。
  • Source_Host、Source_Port、Channel_Name。
  • Last_IO_Error、Last_IO_Errno、Last_IO_Error_Timestamp。
  • Last_SQL_Error、Last_SQL_Errno、Last_SQL_Error_Timestamp。
  • Retrieved_Gtid_Set、Executed_Gtid_Set。
  • Relay_Source_Log_File、Exec_Source_Log_Pos。
  • Auto_Position、Using_Gtid、Source_Log_File、Read_Source_Log_Pos。

如果是早于 8.0.23 的版本,执行 SHOW SLAVE STATUSG,并把 Slave_IO_Running、Slave_SQL_Running 等旧字段映射到上述含义。不要同时在同一实例上随意切换命令,避免把输出混在一起。

2. 用机器可读方式提取关键字段

下面的命令依赖 mysql 客户端的批量输出和 awk。它不会修改复制状态:


			   bash#!/usr/bin/env bash set -euo pipefail MYSQL_HOST="<副本地址>" MYSQL_PORT="<副本端口>" MYSQL_USER="<只读排障用户>" mysql    --host="$MYSQL_HOST"    --port="$MYSQL_PORT"    --user="$MYSQL_USER"    --password    --connect-timeout=5    --batch --raw    -e 'SHOW REPLICA STATUSG'    | awk -F': ' '       /^(Channel_Name|Replica_IO_Running|Replica_SQL_Running|Replica_SQL_Running_State|Seconds_Behind_Source|Last_IO_Error|Last_SQL_Error|Retrieved_Gtid_Set|Executed_Gtid_Set|Relay_Source_Log_File|Exec_Source_Log_Pos|Auto_Position|Using_Gtid):/ {         print $1 "=" $2       }     ' 

如果版本不支持 SHOW REPLICA STATUS,替换为 SHOW SLAVE STATUS。awk 的字段提取适用于常见的 key: value 输出;多行错误信息可能被截断或分拆,因此完整原始输出仍必须保存。

3. 四种最早的分支

可以按下面的证据快速分支:

现象 优先方向
Replica_IO_Running=No,Last_IO_Error 有内容 网络、权限、源库连接、日志文件缺失
IO=Yes,SQL=No,Last_SQL_Error 有内容 SQL 应用错误、冲突、表结构或权限
IO=Yes,SQL=Yes,Seconds_Behind_Source 上升 副本吞吐不足、长事务、锁、读流量或源库写入突增
IO=Yes,SQL=Yes,Seconds 为 NULL 复制刚启动、源库无新事务、时间或状态字段异常

这只是缩小范围,不是根因结论。根因必须由错误日志、worker 状态、GTID、事务和资源指标证明。

四、查 I/O 线程:复制有没有把事件拉到副本

1. 连接线程异常时先读错误,不要重置复制

只要 Replica_IO_Running 为 No,先看错误字段和 MySQL 错误日志:


			   sqlSELECT   CHANNEL_NAME,   SERVICE_STATE,   THREAD_ID,   LAST_ERROR_NUMBER,   LAST_ERROR_MESSAGE,   LAST_ERROR_TIMESTAMP,   RECEIVED_TRANSACTION_SET FROM performance_schema.replication_connection_statusG 

performance_schema 表可以补充通道级信息,但不是所有字段在所有 MySQL 版本都一致。查询报列不存在时,用 DESCRIBE performance_schema.replication_connection_status; 先确认列名。

2. 查看副本错误日志的同一时间窗口

错误日志位置由 log_error 配置决定。先让 MySQL 告诉你路径,再读取指定时间窗口:


			   sqlSHOW VARIABLES LIKE 'log_error'; SHOW VARIABLES LIKE 'log_error_verbosity'; 

			   bash#!/usr/bin/env bash set -euo pipefail ERROR_LOG="" START_TIME="<开始时间>" END_TIME="<结束时间>" awk -v start="$START_TIME" -v end="$END_TIME" '   $0 >= start && $0 <= end {print} ' "$ERROR_LOG"    | grep -Ei 'replica|slave|source|connect|access denied|fatal|error|relay|binlog' 

awk 按日志首列时间比较只适用于日志以可比较的 ISO 时间开头;错误日志使用其他格式时改用 journalctl、grep 或 MySQL 日志组件的实际格式。grep 没有匹配到内容不代表“没有问题”,还要检查时间格式和文件是否滚动。

3. 观察源库是否仍在产生二进制日志

在 source 上查询当前 binary log 位置。MySQL 8.0 常用 SHOW MASTER STATUS;较新版本可能提供 SHOW BINARY LOG STATUS,使用前以 SHOW HELP 或版本文档确认。


			   sqlSHOW MASTER STATUSG SHOW BINARY LOGS; 

如果源库位置持续前进,而副本 Read_Source_Log_Pos 长时间不变,说明接收线程、网络或副本写 relay log 可能受阻。如果副本已经接收但 Executed_Gtid_Set 不前进,则问题在 SQL 应用路径。

五、查 SQL 线程:事件拉到了,但应用不动

1. 先看 coordinator 和 worker

多线程复制时,一个 worker 出错或被长事务阻塞,Seconds_Behind_Source 可能只显示整体结果。使用 performance_schema 查看协调线程和 worker:


			   sqlSELECT   CHANNEL_NAME,   THREAD_ID,   SERVICE_STATE,   LAST_ERROR_NUMBER,   LAST_ERROR_MESSAGE,   LAST_ERROR_TIMESTAMP FROM performance_schema.replication_applier_status_by_coordinatorG SELECT   CHANNEL_NAME,   WORKER_ID,   THREAD_ID,   SERVICE_STATE,   LAST_ERROR_NUMBER,   LAST_ERROR_MESSAGE,   LAST_ERROR_TIMESTAMP,   APPLYING_TRANSACTION,   LAST_APPLIED_TRANSACTION,   LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP FROM performance_schema.replication_applier_status_by_worker ORDER BY WORKER_IDG 

某个 worker 的 LAST_ERROR_NUMBER 非零、LAST_ERROR_MESSAGE 有内容,通常比 Seconds_Behind_Source 更接近复制中断根因。多个 worker 都在运行但最后应用事务长时间不变,则需要查锁、磁盘和单个大事务。

2. 检查副本是否被锁等待拖住

MySQL 8.0 的 performance_schema.data_lock_waits 和 data_locks 能显示 InnoDB 锁等待关系:


			   sqlSELECT   rw.REQUESTING_ENGINE_TRANSACTION_ID AS waiting_trx_id,   rw.BLOCKING_ENGINE_TRANSACTION_ID AS blocking_trx_id,   r.OBJECT_SCHEMA AS waiting_schema,   r.OBJECT_NAME AS waiting_table,   r.INDEX_NAME AS waiting_index,   r.LOCK_TYPE AS waiting_lock_type,   r.LOCK_MODE AS waiting_lock_mode,   r.LOCK_DATA AS waiting_lock_data FROM performance_schema.data_lock_waits AS rw JOIN performance_schema.data_locks AS r   ON r.ENGINE_LOCK_ID = rw.REQUESTING_ENGINE_LOCK_ID WHERE r.OBJECT_SCHEMA IS NOT NULL; 

这条查询展示等待关系,但要得到阻塞事务的线程和 SQL,还需要继续关联 performance_schema.threads、events_statements_current 或 information_schema.innodb_trx。权限不足时结果可能为空。不要为了查锁临时关闭 performance_schema 采集。

3. 查看副本上的长事务和当前语句


			   sqlSELECT   trx_id,   trx_started,   trx_state,   trx_mysql_thread_id,   trx_rows_locked,   trx_rows_modified,   trx_query FROM information_schema.innodb_trx ORDER BY trx_started LIMIT 20; SELECT   THREAD_ID,   EVENT_NAME,   SQL_TEXT,   TIMER_WAIT / 1000000000000 AS seconds_running FROM performance_schema.events_statements_current WHERE SQL_TEXT IS NOT NULL ORDER BY TIMER_WAIT DESC LIMIT 20; 

副本上的读事务可能阻塞 purge 或占用资源,但不一定直接阻塞复制写入;是否冲突要看表、索引和锁隔离级别。一个很大的 trx_rows_modified 或很早的 trx_started 是线索,不是自动 kill 的依据。

六、判断是单个大事务还是持续写入追不上

1. 观察源库写入强度

在 source 上查看一组累计状态,并在数分钟后再次采样计算速率:


			   sqlSHOW GLOBAL STATUS WHERE Variable_name IN (   'Com_insert',   'Com_update',   'Com_delete',   'Com_commit',   'Threads_connected',   'Threads_running',   'Binlog_bytes_written',   'Binlog_commits',   'Binlog_group_commits',   'Binlog_cache_use',   'Binlog_cache_disk_use' ); 

这些是累计计数器,单次读数不能说明当前速率。Binlog_bytes_written 或提交计数在窗口内快速增长,说明源库写入强度可能上升;Threads_running 上升则要继续查锁、慢 SQL 和连接池,不能直接等同于复制延迟。

2. 识别源库长事务


			   sqlSELECT   trx_id,   trx_started,   TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS age_seconds,   trx_state,   trx_mysql_thread_id,   trx_rows_locked,   trx_rows_modified,   trx_query FROM information_schema.innodb_trx ORDER BY trx_started LIMIT 20; 

一个长事务可能造成大 binlog 事务、锁等待和副本应用延迟。不要看到 age_seconds 大就直接终止;先确认连接归属、业务影响、是否为在线 DDL 或批处理,以及 kill 后事务回滚可能带来的额外时间。

3. 查看源库阻塞和慢语句


			   sqlSELECT   p.ID,   p.USER,   p.HOST,   p.DB,   p.COMMAND,   p.TIME,   p.STATE,   p.INFO FROM information_schema.PROCESSLIST AS p WHERE p.COMMAND <> 'Sleep' ORDER BY p.TIME DESC; 

PROCESSLIST 的 TIME 是当前状态持续时间,不一定是语句总耗时;INFO 可能被截断。需要执行计划时,应在低风险环境对同一 SQL 使用 EXPLAIN 或 EXPLAIN ANALYZE,不要在生产副本上对大表随意执行会真正运行语句的分析操作。

七、检查副本自身的磁盘、读流量和资源争用

1. 副本读流量可能和 SQL 应用线程争抢资源

只读副本通常同时承担查询流量。复用上一节的 PROCESSLIST 查询,在副本上执行并按同一时间窗口观察。长时间 Sending data 不一定代表网络发送,它可能表示执行扫描或读取数据。大量查询、临时表、排序和 buffer pool miss 会让复制 SQL 线程拿不到 CPU、锁或磁盘。

2. 查看副本 InnoDB 和临时表压力


			   sqlSHOW GLOBAL STATUS WHERE Variable_name IN (   'Threads_running',   'Created_tmp_tables',   'Created_tmp_disk_tables',   'Innodb_buffer_pool_reads',   'Innodb_buffer_pool_read_requests',   'Innodb_row_lock_current_waits',   'Innodb_row_lock_time',   'Innodb_os_log_pending_fsyncs',   'Innodb_data_pending_reads',   'Innodb_data_pending_writes',   'Innodb_buffer_pool_pages_dirty' ); 

Created_tmp_disk_tables、pending reads/writes 和 row lock 相关计数需要与时间窗口比较。累计值高不代表当前一定异常。若读查询导致磁盘延迟或锁等待上升,应优先做流量降级或把查询迁移到其他副本;这属于业务变更,需要确认连接池和路由回滚方式。

3. 在操作系统层确认设备延迟


			   bash#!/usr/bin/env bash set -euo pipefail iostat -xz 1 10 df -hT df -ih 

iostat 来自 sysstat,字段含义以本机版本为准。重点看副本承载 MySQL 数据目录、relay log 和临时目录的设备。df -hT 与 df -ih 分别检查字节空间和 inode;磁盘快满或 inode 用尽都可能让 relay log 写入异常。

八、确认复制配置没有被意外改变

1. 比对 source 和 replica 的关键变量


			   sqlSHOW VARIABLES WHERE Variable_name IN (   'server_id',   'server_uuid',   'gtid_mode',   'enforce_gtid_consistency',   'binlog_format',   'sync_binlog',   'innodb_flush_log_at_trx_commit',   'replica_parallel_workers',   'replica_parallel_type',   'replica_preserve_commit_order',   'replica_pending_jobs_size_max',   'replica_transaction_retries' ); 

并行 worker 数量、提交顺序和事务重试策略会影响应用吞吐与一致性语义。不要为了追延迟直接把 replica_parallel_workers 调到最大;需要先看 CPU、磁盘、锁和事务冲突,修改后还要观察回放顺序与错误。

2. 检查复制过滤器和表结构


			   sqlSHOW VARIABLES WHERE Variable_name LIKE '%replicate%'    OR Variable_name LIKE '%binlog_do%'    OR Variable_name LIKE '%binlog_ignore%'; SHOW CREATE TABLE <库名>.<表名>G 

过滤器差异可能使副本故意不应用某些库表;表结构差异则可能导致 SQL 线程报错。SHOW CREATE TABLE 只读,但 <库名> 和 <表名> 必须替换为明确对象,不能把用户输入未经校验拼接到自动脚本中。

3. 检查是否有人在副本执行写入


			   sqlSELECT   @@global.read_only AS read_only,   @@global.super_read_only AS super_read_only,   @@session.sql_log_bin AS sql_log_bin; SELECT   USER,   HOST,   COMMAND,   COUNT(*AS connections FROM information_schema.PROCESSLIST GROUP BY USER, HOST, COMMAND ORDER BY connections DESC; 

副本被误写可能产生复制冲突或消耗资源。read_only 和 super_read_only 的具体行为与账号权限有关,不能只看变量名下结论。任何修改只读状态的动作都属于高风险操作,必须先确认切换流程、备份和回滚。

九、用 GTID 判断“已经收到多少”和“已经执行多少”

1. 读取 GTID 集合


			   sqlSELECT   @@global.gtid_executed AS server_gtid_executed,   @@global.gtid_purged AS server_gtid_purged; SHOW REPLICA STATUSG 

Retrieved_Gtid_Set 代表副本接收过的事务集合,Executed_Gtid_Set 代表已执行集合。两者存在明显差异时,说明事件已经进入副本但尚未全部应用。GTID 集合是区间字符串,不能简单用字符串长度当作事务数;应使用 MySQL 的 GTID 函数或专门工具解析,并注意不同 UUID 和区间。

2. 用 GTID 等待做有限验证

如果需要验证某个明确 GTID 是否已经执行,可以在副本上使用 WAIT_FOR_EXECUTED_GTID_SET。它会等待,属于有阻塞时间的检查,必须设置超时:


			   sqlSELECT WAIT_FOR_EXECUTED_GTID_SET(   '<源库UUID>:<事务编号>',   5 AS wait_seconds; 

返回 0 表示在超时时间内已满足,返回 1 表示超时,返回 NULL 表示参数或执行状态异常。不要把未知的 GTID 字符串直接填入生产查询;先从 SHOW REPLICA STATUS 的实际输出中确认。等待本身不会修复复制,只能验证执行进度。

十、一个十分钟只读采集脚本

下面脚本假设 MySQL 8.0.23 及以后,默认在副本执行,收集复制状态、worker、锁、事务和系统 I/O。它不执行 STOP REPLICA、START REPLICA、KILL 或配置修改。


			   bash#!/usr/bin/env bash set -euo pipefail MYSQL_HOST="<副本地址>" MYSQL_PORT="<副本端口>" MYSQL_USER="<只读排障用户>" OUT_DIR="/tmp/mysql-replica-evidence-$(date +%Y%m%d%H%M%S)" mkdir -p "$OUT_DIR" umask 077 run_mysql() {   mysql --host="$MYSQL_HOST" --port="$MYSQL_PORT"      --user="$MYSQL_USER" --password --connect-timeout=5      --batch --raw "$@" } run_mysql -e 'SELECT NOW(), VERSION(), @@hostname, @@server_uuid;'    > "$OUT_DIR/identity.txt" run_mysql -e 'SHOW REPLICA STATUSG'    > "$OUT_DIR/replica-status.txt" run_mysql -e '   SELECT CHANNEL_NAME, WORKER_ID, SERVICE_STATE,          LAST_ERROR_NUMBER, LAST_ERROR_MESSAGE,          LAST_ERROR_TIMESTAMP, APPLYING_TRANSACTION,          LAST_APPLIED_TRANSACTION   FROM performance_schema.replication_applier_status_by_worker   ORDER BY WORKER_ID; ' > "$OUT_DIR/worker-status.txt" run_mysql -e '   SELECT trx_id, trx_started, trx_state, trx_mysql_thread_id,          trx_rows_locked, trx_rows_modified, trx_query   FROM information_schema.innodb_trx   ORDER BY trx_started   LIMIT 50; ' > "$OUT_DIR/innodb-trx.txt" run_mysql -e 'SHOW FULL PROCESSLIST;'    > "$OUT_DIR/processlist.txt" run_mysql -e "   SHOW GLOBAL STATUS   WHERE Variable_name IN (     'Threads_running', 'Created_tmp_disk_tables',     'Innodb_row_lock_current_waits', 'Innodb_os_log_pending_fsyncs',     'Innodb_data_pending_reads', 'Innodb_data_pending_writes',     'Innodb_buffer_pool_pages_dirty'   ); " > "$OUT_DIR/status.txt" iostat -xz 1 10 > "$OUT_DIR/iostat.txt" df -hT > "$OUT_DIR/df-h.txt" df -ih > "$OUT_DIR/df-i.txt" tar -C "$(dirname "$OUT_DIR")" -czf "$OUT_DIR.tar.gz" "$(basename "$OUT_DIR")" printf 'evidence archive: %s ' "$OUT_DIR.tar.gz" 

脚本中的密码仍由 mysql 客户端交互读取,不能在无人值守环境直接使用。若账号没有 performance_schema 权限,脚本可能在中途失败;可把只读查询拆开执行并记录失败项。压缩包包含 processlist 和 SQL 文本,属于敏感运维数据,应限制权限和保存期限。

十一、十分钟内的判断分支

现象 先看什么 下一步
Replica_IO_Running=No Last_IO_Error、源库连接、日志保留、relay log 磁盘 查认证、网络、日志文件和空间;证据清楚前不要 RESET REPLICA 或重新指向 source
Replica_SQL_Running=No Last_SQL_Error、Last_SQL_Errno、worker、当前 GTID 查表结构、唯一键、权限、存储引擎和锁;不要把错误直接跳过
两线程运行但延迟上升 source binlog/提交速率与 replica 应用进度 判断源库写入突增,还是副本读流量、磁盘、锁或并行冲突
Seconds_Behind_Source=NULL 两线程状态、错误字段、GTID 集合、source 是否有新事务 线程正常且无新事务时,用明确 GTID 或位点做验证

线程报错时必须保存错误号、错误文本、worker 状态和 GTID。跳过事务会让副本与 source 产生差异;只有完成数据校验、取得负责人批准并准备补偿方案后,才进入专门的跳过或重建流程。一次 SHOW STATUS 的累计值不能比较速率,应间隔 30~60 秒重复采样。

十二、缓解动作:优先减少竞争,再考虑复制线程操作

1. 先把读流量从副本摘出

如果证据表明副本读查询导致磁盘、CPU、锁或临时表压力,可以先在连接池、代理或服务发现层把该实例标记为不可读。这个动作比停止复制更容易回滚,但仍可能影响连接分布和整体容量。

执行前检查:

  • 其他副本是否有足够容量。
  • 读流量是否会回到 source。
  • 连接池是否会复用已有连接。
  • 代理健康检查是否真正识别摘除状态。
  • 是否能保留路由变更前后的时间戳和配置。

执行后验证错误率、连接数、Threads_running、iostat 和复制应用进度。不要只看代理显示的权重下降。

2. 谨慎处理长查询

如果确认某个查询阻塞复制或耗尽副本资源,可以通过应用层取消、连接池超时或经授权的 KILL QUERY 处理。KILL 是有风险的,查询可能持有事务或触发回滚。


			   sqlSELECT   ID,   USER,   HOST,   DB,   COMMAND,   TIME,   STATE,   INFO FROM information_schema.PROCESSLIST WHERE ID = <线程ID>G KILL QUERY <线程ID>; 

<线程ID> 必须来自当前实例、当前时间窗口的 PROCESSLIST,并由有权限人员确认。KILL QUERY 只终止当前语句,连接可能仍然存在;若要 KILL CONNECTION,风险更大。执行后应查原事务是否回滚、锁是否释放、复制 worker 是否继续。

3. 何时可以 START REPLICA

如果之前只是计划性 STOP、网络已经恢复、错误原因已消除,并且保存了状态,才考虑 START REPLICA。不要把 START REPLICA 当作所有延迟问题的通用修复。


			   sqlSTART REPLICA; SHOW REPLICA STATUSG 

START REPLICA 可能同时启动 IO 和 SQL 线程,具体行为按版本和参数确定。执行后至少观察数个采样窗口,确认两个线程持续运行、Last_Error 为空、GTID 执行集合前进、延迟不再扩大,并检查副本资源是否被回放冲高。

4. 停止复制是高风险动作

STOP REPLICA 会让副本停止接收或应用事务,具体停止哪个线程取决于是否指定 IO_THREAD 或 SQL_THREAD。它可能扩大延迟、占满 relay log,也可能影响故障切换可用性。只有在要保护副本、处理明确错误或执行受控维护时使用。


			   sqlSHOW REPLICA STATUSG STOP REPLICA SQL_THREAD; SHOW REPLICA STATUSG 

执行前保存完整状态、确认 relay log 剩余空间、确认不会把该实例误当作健康副本,并通知依赖方。执行后记录停止时间和原因。恢复使用 START REPLICA SQL_THREAD,并按 GTID、worker 和业务读流量验证。

十三、不要在证据不足时跳过事务

跳过事务会改变 Executed_Gtid_Set 与 source 的数据语义。GTID 模式下使用 sql_replica_skip_counter 通常不是合适的通用手段;不同复制模式和版本的处理方式不同,可能需要注入空事务、修复数据后重新执行或从备份重建副本。

任何跳过动作至少需要:

  1. 记录出错 GTID、事务内容、影响表和错误号。
  2. 判断事务是否已经在 source 成功提交。
  3. 评估副本是否已经存在部分效果。
  4. 备份相关表或取得可验证的快照。
  5. 完成 source/replica 数据校验或补偿。
  6. 得到业务负责人和数据库负责人批准。
  7. 记录可恢复路径,而不是只记录复制恢复。

如果事务是 DDL、权限变更、账务写入或关键配置变更,通常不应在值班电话中直接跳过。更安全的选择可能是隔离副本、重建副本或先修复数据。

十四、复制配置变更的备份和回滚

1. 先保存复制状态


			   bash#!/usr/bin/env bash set -euo pipefail MYSQL_HOST="<副本地址>" MYSQL_PORT="<副本端口>" MYSQL_USER="<只读排障用户>" OUT_FILE="/tmp/replica-before-change-$(date +%Y%m%d%H%M%S).txt" mysql    --host="$MYSQL_HOST"    --port="$MYSQL_PORT"    --user="$MYSQL_USER"    --password    --connect-timeout=5    --batch --raw    -e '     SELECT NOW(), VERSION(), @@hostname, @@server_uuid;     SHOW REPLICA STATUSG     SHOW VARIABLES LIKE "replica%";     SHOW VARIABLES LIKE "gtid%";   ' > "$OUT_FILE" chmod 600 "$OUT_FILE" printf 'saved: %s ' "$OUT_FILE" 

这是变更前状态记录,不是数据备份。如果要做表修复、重建副本或切换,仍需按数据库备份策略取得可恢复备份或快照。复制状态文件不能替代备份。

2. 查看当前 source 配置再决定是否 CHANGE


			   sqlSHOW REPLICA STATUSG SHOW VARIABLES LIKE 'source%'; SHOW VARIABLES LIKE 'master%'; SHOW VARIABLES LIKE 'report%'; 

CHANGE REPLICATION SOURCE TO 会改变连接、日志位置、GTID 自动定位或 TLS 参数,是高风险变更。不要用网上复制的整条命令覆盖所有参数,尤其不要在 GTID 自动定位环境中随意指定 file/position。只有明确知道当前模式、备份和回滚方式时才执行。

十五、最小恢复验证清单

复制线程恢复后,按顺序验证,而不是只看一行 Seconds_Behind_Source:

  1. IO、SQL 线程和 worker 连续运行,错误字段没有新增内容。
  2. Retrieved_Gtid_Set 持续接收,Executed_Gtid_Set 追上或缩小差距。
  3. source 写入与 replica 应用速率平衡,磁盘、锁等待和 Threads_running 回到可接受范围。
  4. 恢复读流量前验证关键查询、数据新鲜度、连接池和代理健康状态。

可以使用如下只读 SQL 做连续验证:


			   sqlSELECT NOW() AS checked_at; SHOW REPLICA STATUSG SELECT   CHANNEL_NAME,   WORKER_ID,   SERVICE_STATE,   LAST_ERROR_NUMBER,   LAST_ERROR_MESSAGE,   LAST_APPLIED_TRANSACTION,   LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP FROM performance_schema.replication_applier_status_by_worker ORDER BY WORKER_ID; 

需要把每次执行的时间和结果保存下来,才能判断是前进、停滞还是反复。若错误字段为空但 GTID 不前进,继续查源库是否有新事务、副本是否正在等待大事务提交、是否存在读流量或设备瓶颈。

十六、连续采样确认趋势

如果要判断 GTID、线程状态和累计计数是前进还是停滞,应在同一实例每 30 秒重复执行一次 SHOW REPLICA STATUS 和关键状态查询,至少持续十分钟。运行时记录采样时间,不要用一次 SHOW GLOBAL STATUS 的累计值比较速率。一个只读排障连接通常足够,避免在多台实例同时启动大量采集连接;输出包含错误字段和主机信息,应按敏感数据处理。

十七、哪些信号说明应该升级,而不是继续等

出现以下任一情况,应尽快升级到数据库负责人或切换负责人:

  • IO 线程或 SQL 线程持续停止,且错误原因不是已知的短暂维护。
  • relay log 或数据盘剩余空间接近保护阈值。
  • Last_SQL_Error 指向表结构、唯一键、存储引擎或数据一致性问题。
  • GTID 差距持续扩大,副本应用速率明显低于 source 写入速率。
  • 单个事务持续时间很长,或副本读流量无法摘除并持续争抢资源。
  • 当前副本是唯一可切换副本,延迟已经超过业务允许的新鲜度。

“再等五分钟”只有在复制线程正常、GTID 差距在缩小、磁盘和锁压力可控时才是合理选择。线程正常但差距持续扩大,等待只是让恢复成本更高。

十八、四个高风险误区

  • 直接重启 mysqld 只会丢现场,不能修复表结构、GTID 缺口或源库日志缺失;重启前要完成证据保存、备份和变更评估。
  • 只看 Seconds_Behind_Source 无法区分 I/O 中断、SQL 报错、源库无新事务和 worker 不均衡,必须保存完整复制状态。
  • 直接增加并行 worker 可能放大锁、磁盘或提交顺序竞争;先核对并行参数和资源,再做灰度变更。
  • 直接跳过错误事务会把复制恢复和数据正确混为一谈。关键表、DDL、账务写入和权限变更应优先修复或重建,不要在电话处理中擅自跳过。

结语:十分钟的价值是把猜测变成分支

主从延迟突然拉大时,最有效的动作不是寻找一条神奇命令,而是按顺序把范围缩小:

复制状态 → I/O 线程还是 SQL 线程 → GTID 已接收还是已执行 → worker、锁还是大事务 → source 写入还是 replica 资源 → 低风险缓解还是数据库级故障流程

只要保留完整状态、错误字段、GTID、事务和资源证据,就能避免把一个延迟数字误判成单一根因。十分钟内不一定能修好所有复制故障,但应该能明确下一步是安全等待、隔离读流量、处理已确认的异常,还是停止继续尝试并升级。

 


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分