无主键表导致从库DELETE慢到停摆,根本原因是ROW模式下从库无法定位行而被迫全表扫描;即使有普通索引,仍需逐行比对binlog事件,100万行删除在从库可能触发千万级扫描,耗时数小时。

主从复制延迟本身不会导致索引“失效”,但会放大无主键表在基于索引的 DML 操作(如 DELETE、UPDATE)下的执行效率坍塌,最终表现为从库 SQL 线程卡死、延迟飙升甚至中断。修复重点不是重建索引,而是切断「无主键 + 范围索引操作 + 单线程回放」这个连锁反应链。
为什么无主键表会让DELETE WHERE date_col BETWEEN ...在从库慢到停摆?
MySQL 从库回放基于行格式(binlog_format=ROW)的事件时,若目标表无主键或唯一键,SQL 线程无法用索引快速定位待删行,只能退化为全表扫描(TABLE_SCAN)。即使有 startdate 索引,InnoDB 仍需逐行比对每条中继日志里的变更记录——100 万行删除,在主库走索引 2 秒完成;在从库可能触发千万级行扫描,耗时数小时甚至失败。
- 典型现象:
SHOW PROCESSLIST中 SQL 线程状态长期卡在Updating或Deleting,Relay_Log_Space增长但Seconds_Behind_Master不动 - 确认方式:在从库执行
SELECT * FROM performance_schema.events_statements_current WHERE THREAD_ID IN (SELECT THREAD_ID FROM performance_schema.threads WHERE TYPE='FOREGROUND' AND PROCESSLIST_INFO LIKE 'Delete%');,看ROWS_AFFECTED是否异常高 - 关键参数:
slave_rows_search_algorithms='TABLE_SCAN,INDEX_SCAN'(默认值)意味着它宁可全表扫也不放弃索引,但无主键时索引扫描实际无效
紧急止损:临时绕过 SQL 线程阻塞
当从库已卡住且业务不可停,优先让复制流恢复流动,再处理数据一致性。不能直接 START SLAVE 等它自己跑完——那可能永远等不到。
- 先停掉阻塞源:在从库执行
STOP SLAVE;,避免 relay log 继续堆积 - 跳过当前坏事务(仅限确认可丢弃):若为 GTID 模式,用
SET GTID_NEXT='xxx:yyy'; BEGIN; COMMIT;注入空事务;若为 position 模式,用SET GLOBAL sql_slave_skip_counter = 1;后START SLAVE; - 但注意:跳过只是让 SQL 线程往前走,被跳过的删除操作在从库仍存在,后续必须人工补删,否则主从数据不一致
- 切勿在跳过前重启从库——
sql_slave_skip_counter是会话级变量,重启即丢失
根治方案:强制主键 + 并行回放 + 分批操作
修复不是修一条 SQL,而是改三处:表结构、复制机制、运维习惯。
- 给问题表加主键:
ALTER TABLE db1.t20250121 ADD COLUMN id_new BIGINT AUTO_INCREMENT PRIMARY KEY FIRST;(注意:8.0+ 支持ALGORITHM=INSTANT,5.7 需评估锁表时间) - 启用 WRITESET 并行复制(MySQL 8.0+):
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK'; SET GLOBAL slave_parallel_workers = 16;,让同表的非冲突事务并行回放 - 今后所有范围 DML 必须分批:
DELETE FROM t20250121 WHERE startdate >= '2024-12-01' AND startdate ,配合循环脚本执行,避免单事务产生巨量行事件 - 验证效果:执行后观察
SHOW SLAVE STATUS\G中Slave_SQL_Running_State是否频繁切换,而非长时间停留在某一行
容易被忽略的兼容性陷阱
很多团队在加主键后发现延迟依旧,往往栽在两个隐蔽点上:
-
slave_parallel_workers设了但slave_parallel_type还是DATABASE——并行完全不生效,必须显式设为LOGICAL_CLOCK或WRITESET - 主库未开启
binlog_transaction_dependency_tracking=WRITESET,导致从库即使配了 WRITESET 也无法识别事务依赖关系,退化为单线程 - 应用层仍在用旧连接直连从库写入,造成主从数据错乱,此时任何复制优化都白搭——得先切走读流量、封禁从库写权限
真正耗时的从来不是执行命令,而是确认那张表到底有没有主键、那个变量到底有没有生效、那个连接到底是不是还在往从库写。


















