单次大delete会锁死数据库,因innodb在百万级删除时可能升级锁粒度且事务长期持锁,阻塞其他读写;同时undo/redo日志爆炸导致io过载、主从延迟甚至服务不可用。

因为不分批会导致锁表、日志爆炸、主从延迟甚至服务不可用——这不是性能问题,是稳定性风险。
为什么单次 DELETE 会锁死数据库?
InnoDB 的 DELETE 是行级锁,但当删除量过大(比如百万级),引擎可能升级为间隙锁或表锁;更关键的是,整个操作在一个事务里,锁会一直持有到事务结束。此时所有对被删数据范围的读写都会阻塞。
- 现象:
SHOW PROCESSLIST中看到大量Waiting for table metadata lock或Updating状态 - 原因:未提交事务持续占用 undo log 和锁资源,其他查询被迫排队
- 特别注意:
autocommit=0时,哪怕只执行一条DELETE ... LIMIT 1000,不COMMIT就等于长期持锁
redo/undo 日志为什么会撑爆磁盘?
每删一行,InnoDB 都要写 undo log(用于回滚)和 redo log(用于崩溃恢复)。删 100 万行 ≈ 写 100 万条日志记录,IO 压力瞬间拉满,innodb_log_file_size 不够时还会频繁刷盘、卡住写入。
- 典型报错:
MySQL server has gone away或Lock wait timeout exceeded - 影响:不仅拖慢当前操作,还可能让 binlog 写入延迟,导致主从同步中断
- 验证方式:监控
SHOW ENGINE INNODB STATUS中的LOG段,看log sequence number增速是否异常
分批提交的硬性参数怎么设才安全?
没有通用值,但必须按实际负载动态调。核心是让每批「删除 → 提交 → 休眠」形成闭环,不能只靠 LIMIT。
-
LIMIT值:500~2000 之间起步,优先选 1000;若表有复合索引且条件过滤强,可试 5000 -
SLEEP()时间:0.1s 起步,观察iostat -x 1的%util,超过 80% 就加到 0.3~0.5s - 事务控制:确保
autocommit=1,或显式COMMIT—— 存储过程中漏写COMMIT比漏写SLEEP更危险 - 索引依赖:WHERE 条件字段必须有索引,否则每次
LIMIT都要全表扫描,越删越慢
容易被忽略的坑:ORDER BY + LIMIT 组合
如果删除条件无法走索引,又想保证顺序(比如按时间删旧数据),ORDER BY 会强制排序,极大拖慢速度。正确做法是用主键范围切片,而不是靠 ORDER BY create_time LIMIT 1000。
- 错误写法:
DELETE FROM log WHERE status='error' ORDER BY id LIMIT 1000(没索引时扫全表) - 正确思路:先查出待删 ID 范围
SELECT MIN(id), MAX(id) FROM log WHERE status='error' AND id BETWEEN ? AND ?,再按段删 - 极端情况:表无主键?必须先加自增列或用
ROW_NUMBER()(MySQL 8.0+)生成临时序号,否则分批逻辑不可靠
真正难的不是写存储过程,而是判断「这次删多少、停多久、要不要调索引」——这些得看实时监控,而不是套模板。











