直接delete from table where...容易出事,因其会立刻变成长事务,引发锁行、日志暴涨、主从延迟跳涨及dn自动中断保护;常见现象包括执行卡住、updating状态、锁等待飙升和从库延迟超30分钟。

为什么直接 DELETE FROM table WHERE ... 容易出事
因为大表上没控制的 DELETE 会立刻变成长事务:锁行、涨日志、拖慢复制、甚至触发 DN 的自动中断保护。你看到 @@ROWCOUNT 返回几万,但监控里 CPU 和磁盘 IO 已经飙红,从库延迟开始跳涨——这不是删得快,是系统在硬扛。
常见错误现象包括:DELETE 执行十几分钟没结束、SHOW PROCESSLIST 里卡在 Updating 状态、innodb_row_lock_time_avg 暴增、主从延迟突增 30 分钟以上。
- 没加
WHERE条件?线上环境等同于删库跑路预备动作 - 条件字段没索引?全表扫描 + 行锁堆积,其他查询全被堵死
- 一次删超 10 万行?InnoDB 日志写入压力翻倍,checkpoint 可能跟不上
分批删除(Chunking)怎么写才稳
核心不是“删多少”,而是“怎么切”和“怎么控”。用主键或带索引的单调字段(如 id、created_at)做分片依据,每次只删一个可控区间。
MySQL 示例(安全闭环写法):
DELETE FROM logs WHERE status = 'archived' AND id BETWEEN 1000000 AND 1004999;
执行后必须查 ROW_COUNT(),为 0 就停,非 0 再递进下一批(如 1005000–1009999)。别用 LIMIT 配合无序 ORDER BY,没索引时排序成本比删除本身还高。
- 批次大小建议 1k–5k 行,视单行体积和 I/O 能力调整
- 批次间加
WAITFOR DELAY '00:00:00.1'(SQL Server)或应用层sleep(0.05),缓解复制压力 - 避免
DELETE ... IN (SELECT ...),先CREATE TEMPORARY TABLE temp_ids AS SELECT id FROM ...,再JOIN删除
分布式数据库里 ExecuteDelete 为什么不能用
ExecuteDelete 是 EF Core 的客户端方法,生成的是单节点 SQL。YashanDB、TiDB 等协调节点(CN)根本不会把它当分布式语句路由——它要么只在 CN 上删元数据,要么报错拒绝,实际数据还在各数据节点(DN)上原封不动。
你看到返回 0 行影响,日志却安静如鸡,八成就是掉进这个坑了。
- 正确做法:显式写含分片键的
DELETE,如DELETE FROM logs WHERE tenant_id = 123 AND created_at - 禁止用范围条件如
tenant_id > 100,这会触发广播删除,扫全集群 - 多个分片键值(如
tenant_id IN (123, 456))必须拆成多条独立语句并发执行
比 DELETE 更优雅的替代方案
真正大规模清理,优先想“能不能不删”。删是副作用大的操作,归档、分区裁剪、软标记才是生产环境更稳的选择。
比如按天分区的日志表,直接 ALTER TABLE logs DROP PARTITION p20250101,毫秒完成,零日志、不锁表;又比如把 deleted_at 字段加入分片键索引,后续清理仍可走分片路由,不退化为广播。
- 软删除别只加个字段就完事,要确保
deleted_at在查询路径中能命中索引,否则WHERE deleted_at IS NOT NULL仍是全表扫 - TRUNCATE 比 DELETE 快得多,但不可回滚、重置自增、不能带条件——仅适用于整表清空场景
- 归档导出用
INSERT INTO archive SELECT ... LIMIT 10000分批,再删对应批次,I/O 和计算压力可解耦
最常被忽略的一点:所有 DELETE 操作前,必须在从库或备份库上先跑 EXPLAIN DELETE 或等价分析,确认执行计划走的是索引查找,而不是 type: ALL。否则删得越快,翻车越狠。











