安全删除需分批、索引、游标分页:WHERE条件字段必须有有效索引,用id主键游标替代LIMIT,单批1000~5000行并休眠,避免fetchall全量加载。

DELETE 语句不加 LIMIT 就直接崩库?
线上环境执行 DELETE FROM users WHERE status = 'inactive' 这种语句,极大概率触发锁表、主从延迟、甚至 OOM。MySQL 默认事务下,全表扫描+逐行加锁+生成大量 undo log,不是删数据,是给数据库做心肺复苏。
真正安全的做法是分批、带索引条件、控制每批大小和间隔:
- 必须确保
WHERE条件字段有有效索引(比如status单独无索引,但(status, id)联合索引可用) - 用
id或自增主键做游标分页,比LIMIT offset, size更稳定(避免 offset 越大越慢) - 单批建议
1000~5000行,太小效率低,太大锁时间长;每批后SLEEP(0.1)或更久,给复制和缓冲池喘息机会 - 别信“先 SELECT 再 DELETE”——中间可能有新数据写入,要用
SELECT ... FOR UPDATE或原子性子查询兜底
Python 脚本怎么安全跑完十万行删除?
用 mysql-connector-python 或 pymysql 写循环时,最容易犯的错是:把整个结果集一次性 fetchall() 到内存,或者没关游标/连接,脚本跑一半就报 MySQL Server has gone away。
正确姿势是流式处理 + 显式事务控制:
- 每次只查一批待删
id:SELECT id FROM logs WHERE created_at - 拿到这批
id后,用DELETE FROM logs WHERE id IN (1,2,3,...)删除(注意 IN 列表长度别超 max_allowed_packet) - 每次删完立刻
conn.commit(),不要攒事务;出错时捕获pymysql.err.IntegrityError等并跳过异常 ID - 记录最后成功删除的
id到临时文件或数据库,断点续删才靠谱
mysqldump + truncate 不如直接用 pt-archiver
有人想导出再清空,或者用 TRUNCATE TABLE 图快——但 TRUNCATE 是 DDL,会锁整表、重置 AUTO_INCREMENT、且无法加 WHERE 条件;mysqldump --where 导出再删,IO 和磁盘压力反而更大。
pt-archiver 是 Percona 工具链里专干这事的,底层就是分批 + 限流 + 自动重连:
- 命令示例:
pt-archiver --source h=localhost,D=app,t=events --where "created_at - 它会自动按主键切片,避免全表扫描;
--statistics能输出每批耗时、行数、锁等待时间 - 注意:必须保证
--where条件能命中索引,否则它也会退化成全表扫描 - 别在主库直接跑,优先走从库过滤 + 主库删(用
--dest指定主库),减少主库负载
PostgreSQL 怎么避免 VACUUM 堵塞?
PG 的 DELETE 不释放磁盘空间,只是打标记,后续靠 VACUUM 回收。如果批量删太快,VACUUM 跟不上,表膨胀、查询变慢、WAL 日志暴涨都是连锁反应。
关键不是删得多快,而是让 VACUUM 能及时介入:
- 删之前调大
vacuum_cost_limit(比如从默认 200 改到 1000),让后台 vacuum 更积极 - 用
DELETE ... RETURNING id获取删掉的行,再异步触发VACUUM (VERBOSE, ANALYZE) table_name,别等自动 vacuum - 对超大表,考虑分区表:按时间分区后,直接
DROP PARTITION或TRUNCATE PARTITION,秒级完成且无 vacuum 压力 - 监控
pg_stat_all_tables.n_dead_tup,超过总行数 20% 就该干预,别等 autovacuum 慢半拍
分批删除的本质不是“怎么删”,是“怎么不让数据库恨你”。索引是否生效、事务粒度、监控反馈、回滚预案——漏掉任何一环,删着删着就进了故障复盘会。

















