先用SELECT确认要删哪些行是防止误删的唯一防线,必须执行COUNT(*)核对量级、LIMIT 10核对业务逻辑、EXPLAIN检查索引与字段类型匹配性,并配合事务、LIMIT、备份等多重防护措施。

先用SELECT确认要删哪些行
直接写 DELETE FROM users WHERE status = 'inactive' 是高危操作——你根本不知道它会删多少条,也不知道有没有索引支撑。必须先模拟执行:
- 把
DELETE换成SELECT COUNT(*),看数量是否符合预期 - 再换成
SELECT id, status, updated_at LIMIT 10,人工核对数据是否真该删 - WHERE 条件里显式加主键范围,比如
AND id BETWEEN 1000 AND 2000,避免全表扫描锁表 - ORM 用户别直接调
.delete(),先用.values_list('id', flat=True)拿出 ID 列表,分批处理更可控
备份表结构和数据要分开做
只用 CREATE TABLE backup_table AS SELECT * 看似省事,但容易翻车:归档表缺字段、多时间戳、没默认值,后续插入就失败。
- 先用
CREATE TABLE backup_table LIKE original_table复制结构(含索引、约束、NULL 属性) - 再额外加两个字段:
archived_at DATETIME NOT NULL和archived_by VARCHAR(64),否则查不出是谁、什么时候删的 - 插入时别偷懒写
SELECT *,显式列出所有字段,比如INSERT INTO backup_table (id, name, archived_at) VALUES (OLD.id, OLD.name, NOW()) - MySQL 下检查备份文件是否含
SET FOREIGN_KEY_CHECKS=0;,否则还原时外键冲突
触发器归档必须用 BEFORE DELETE
AFTER DELETE 触发时原行已删,归档失败会导致删除语句中断且无法回滚;BEFORE 才能确保“存住再删”。但注意限制:
- MySQL 中
BEFORE DELETE不能在触发器里查本表(报错 ERROR 1442),也不能跨库写入归档表 - 想备份关联从表数据?必须显式
SELECT FROM order_items WHERE order_id = OLD.id,不能指望触发器自动带出 - 外键设了
ON DELETE CASCADE的话,从表数据会被引擎层直接删掉,触发器根本查不到——得先改成ON DELETE RESTRICT - 大表慎用:每删一行都触发一次 INSERT,几万行就能拖垮 I/O,高频日志表应改用定时任务 +
INSERT INTO ... SELECT分批处理
生产环境删数据必须套事务+超时+验证
不加控制的 DELETE 在高并发下等于埋雷,锁等待、主从延迟、长事务都可能让线上查询卡死。
- 一定要用
BEGIN; DELETE ... ; COMMIT;,别依赖自动提交——中断后没法回滚 - MySQL 客户端加
--execution-timeout=30,Python 的pymysql设read_timeout和write_timeout - 删完立刻查
ROW_COUNT(),比对预估数量;再跑SELECT COUNT(*)验证目标表行数是否减少对应值 - 日志里记清楚:谁执行、删了哪张表、WHERE 条件、影响行数、执行耗时——出问题时这是唯一线索
NOT NULL 或少一个默认值,归档就会静默失败;而事务里混了 ALTER TABLE,MySQL 8.0+ 会自动提交,前面的 DELETE 就再也回滚不了。

















