MySQL主从复制过程中CPU突然飙升,很多运维人员的第一反应是业务查询太慢,但这个锅往往甩错了地方。主从复制机制本身并不会直接消耗大量CPU资源,真正导致CPU持续走高的,其实是IO_THREAD或SQL_THREAD卡在某个低效环节里反复空转、频繁重试或执行密集计算。这类问题最让人头疼的地方在于——从SHOW PROCESSLIST里查看,一切似乎都显示正常,但CPU负载就是降不下来。
下面深入拆解三个最常见的“隐形杀手”及其排查思路。
IO_THREAD卡在网络延迟或relay log写入缓慢
主库的Binlog Dump线程持续向从库推送binlog日志,可一旦网络延迟较高、数据包丢失增多,或者从库磁盘写入relay log速度过慢(例如IOPS不足、sync_binlog或sync_relay_log参数设置为1),主库线程就会陷入“等待→检测→重试”的反复循环。此时SHOW PROCESSLIST中常常能看到大量“Master has sent all binlog to slave; waiting for more updates”的线程,表面看似空闲,实际上CPU单核已经跑满。
如何快速确认?直接在从库执行STOP SLAVE IO_THREAD,观察主库CPU是否明显回落——如果下降,问题就出在IO链路上。接着使用tcpdump -i any port 3306抓包,查看是否有大量TCP重传现象。再从SHOW SLAVE STATUS\G中观察Seconds_Behind_Master持续增长,但Slave_IO_Running: Yes,说明IO线程“接收缓慢”,并非连接断开。
SQL_THREAD重放ROW格式日志时引发全表扫描
当binlog_format = ROW且从库表缺少关键索引时,SQL_THREAD每解析一条UPDATE或DELETE语句都需要进行唯一键查找或WHERE条件匹配。如果WHERE id IN (…)涉及的字段没有索引,一次修改几千行就可能触发全表扫描——CPU负载瞬间飙升,但SHOW PROCESSLIST中根本看不到对应的SQL,因为这并非客户端会话,而是后台线程在默默执行。
定位方法非常直接:使用mysqlbinlog --base64-output=DECODE-ROWS -v解析Exec_Master_Log_Pos附近的binlog,检查是否存在大事务或多行变更。然后仔细核对从库上WHERE、JOIN、ORDER BY涉及的字段是否与主库一致创建了索引——缺少一个索引就可能导致性能卡死。如果已经发生大事务,临时缓解措施可以设置slave_parallel_workers = 0,避免并行模式下锁争用进一步放大CPU压力。
GTID模式下SQL_THREAD反复校验事务边界
启用gtid_mode = ON之后,SQL_THREAD每次执行前都需要查询gtid_executed集合,确认当前事务是否已经执行过。如果从库刚刚重启,或者gtid_purged被误清空,它就会回溯大量binlog文件进行字符串比对和集合查找,CPU密集操作主要集中在memcmp和哈希查找上。此时SHOW SLAVE STATUS中的Seconds_Behind_Master可能显示为0,但CPU负载就是居高不下。
快速判断方法:执行SELECT @@gtid_executed;,如果返回结果为空或者远小于主库的SELECT @@gtid_executed;结果,基本可以确定是GTID状态异常。但千万不要盲目执行RESET MASTER——这会清空gtid_executed,反而加重校验负担。正确的做法是,先通过SELECT * FROM performance_schema.events_statements_summary_by_digest观察CPU热点是否集中在GTID相关函数。确认主库gtid_purged完整后,再在从库执行SET GLOBAL gtid_purged = '…'补全缺失的GTID信息。
真正难以排查的场景,就是那种SHOW PROCESSLIST中看不到活跃线程、top命令里MySQL进程CPU占用很高但无法关联到具体SQL的情况。这时候大概率是SQL_THREAD在后台默默进行索引查找或GTID比对——而不是你在业务中写的那条UPDATE在引发问题。
