不能直接批量DELETE因会触发InnoDB行锁升级致全表阻塞,引发超时或日志溢出;分批删需控制每事务行数(500–5000)、及时COMMIT、用ROW_COUNT()判断退出。

为什么不能直接 DELETE FROM table WHERE ... 批量删?
因为大表上一次性删几百万行,会锁住整张表(尤其是 InnoDB 的 gap lock + next-key lock),阻塞所有读写,业务直接卡死。MySQL 默认事务里删太多行,还会触发 Lock wait timeout exceeded 或撑爆 innodb_log_file_size。
分批删的核心不是“快”,是“不抢锁、不拖垮日志、不堵住其他 SQL”。关键在控制每次事务的行数和提交节奏。
WHILE 循环 + LIMIT 是最稳的写法
存储过程中不能直接在 DELETE 里用变量当 LIMIT 参数(MySQL 5.7+ 支持 LIMIT 后接变量,但低版本不行),得靠循环体 + ROW_COUNT() 控制退出条件。
- 先用
SELECT COUNT(*)确认待删总量,避免空跑 - 每次
DELETE ... LIMIT 1000,数值建议 500–5000:太小事务多、开销大;太大仍可能锁太久 - 必须加
COMMIT(或确保 autocommit=1),否则一个事务包到底,锁一直不放 - 删完立刻查
ROW_COUNT(),返回 0 就停,别硬套固定循环次数
DELIMITER $$
CREATE PROCEDURE batch_delete_old_logs()
BEGIN
DECLARE deleted_rows INT DEFAULT 1;
WHILE deleted_rows > 0 DO
DELETE FROM logs
WHERE created_at < '2023-01-01'
LIMIT 1000;
SELECT ROW_COUNT() INTO deleted_rows;
DO SLEEP(0.01); -- 防止 CPU 疯转,可选
END WHILE;
END$$
DELIMITER ;WHERE 条件字段没索引?删再慢也得先加
如果 WHERE 里的字段(比如 created_at)没索引,每次 LIMIT 删除前都要全表扫描——不仅慢,还可能让 MySQL 误判执行计划,锁住更多无关行。
- 用
EXPLAIN DELETE ...(实际要改写成EXPLAIN SELECT对应条件)确认是否走索引 - 没索引就加:
ALTER TABLE logs ADD INDEX idx_created_at (created_at) - 注意:加索引本身会锁表,线上操作得挑低峰或用
ALGORITHM=INPLACE(MySQL 5.6+)
用 pt-archiver 比手写存储过程更安全
纯 SQL 存储过程难处理异常中断、进度跟踪、失败重试。DBA 级批量删,优先用 Percona Toolkit 的 pt-archiver:
- 自带限流(
--sleep、--limit)、自动 commit、断点续删 - 支持把删掉的数据归档到另一张表或文件,方便回溯
- 不会因连接中断导致部分删完不提交,比存储过程容错强得多
- 命令示例:
pt-archiver --source h=localhost,D=mydb,t=logs --where "created_at
真要手写存储过程,记得每次删完检查 information_schema.INNODB_TRX 里有没有长事务残留——那是最容易被忽略的锁源。










