truncate table清空大表瞬间完成,因其是ddl操作,直接释放数据段、重置fseg_header、截断.ibd文件且不写undo日志;delete则逐行加锁、写binlog和undo日志,导致锁争用、日志暴涨及主从延迟。

大批量 DELETE 是主从延迟最隐蔽、杀伤力最强的源头之一——它不显山不露水,却能让 Seconds_Behind_Master 突然跳到几千秒,且监控失真。
为什么 DELETE 大表会卡住从库?
不是因为“删得慢”,而是因为:单个 DELETE 事务在从库 SQL 线程上必须串行执行完,期间整个复制链路冻结;同时它逐行加锁、写 undo log、刷脏页、生成巨量 binlog(尤其 ROW 格式),从库资源被独占。
- 现象:
SHOW PROCESSLIST中从库 SQL 线程长期显示Updating或end,Seconds_Behind_Master却可能卡在 0 或旧值不动 - 根本原因:MySQL 的
SQL Thread(或 MTS 下的 worker)必须等整个事务提交后才更新Exec_Master_Log_Pos和延迟计数 - 更糟的是:
DELETE不释放磁盘空间,后续INSERT可能触发频繁页分裂,进一步拖慢从库回放
TRUNCATE TABLE 什么情况下可以直接用?
它快不是因为“优化了删除”,而是绕过了事务和日志机制——但前提非常严格。
- 必须满足:
无外键引用该表、不需要保留自增 ID 值、可接受不可回滚(哪怕在事务里执行也立即生效) - 权限要求更高:
TRUNCATE需要DROP权限,而不仅是DELETE - binlog 行为:
STATEMENT格式下只记一条语句;ROW格式下不记录(MySQL 5.7+ 默认行为),避免从库重放负担 - 注意版本:
TRUNCATE TABLE t PARTITION (p1)仅 MySQL 8.0+ 支持,低版本不适用
分批 DELETE 必须避开的三个坑
“每次 DELETE ... LIMIT 1000” 是常见但高危做法,容易漏删、重复删、锁表加剧延迟。
- 禁用
LIMIT+OFFSET:如DELETE FROM t ORDER BY id LIMIT 1000 OFFSET 10000—— 每次扫描都从头开始,越往后越慢,且并发写入时范围漂移 - 必须基于单调索引字段分段:
WHERE id BETWEEN ? AND ?,确保范围不重叠、全覆盖、可预测;id必须有索引,否则每批都全表扫描 - 别忽略从库压力:每批
COMMIT后建议加SLEEP(0.05)(非必须但推荐),避免瞬时 I/O 和锁争用打满从库 - 验证执行计划:
EXPLAIN SELECT * FROM t WHERE id BETWEEN 10000 AND 20000,确认走索引;没命中就加索引,别硬扛
从库端必须调的两个并行复制参数
光拆主库事务不够,从库不并行,照样卡在最后一步。
-
slave_parallel_type = LOGICAL_CLOCK:启用基于逻辑时钟的并行,支持跨 schema 并行(比DATABASE类型实用得多) -
slave_parallel_workers = 8(或 CPU 核心数 × 2):线程太少起不到作用;但超过 16 容易因锁竞争反降效 - 配套必须开:
slave_preserve_commit_order = 1,否则并行执行破坏事务提交顺序,导致数据不一致 - 验证是否生效:
SHOW PROCESSLIST中应看到多个Worker线程处于Slave has read all relay log或Executing event状态
真正难的不是“怎么删”,而是判断“能不能删”——删之前先看有没有外键、触发器、分区策略、从库读负载;删之中紧盯 Relay_Log_Space 是否持续增长;删之后用 pt-heartbeat 交叉验证真实延迟,别只信 Seconds_Behind_Master。











