生产环境DELETE必须带WHERE且含时间字段索引,禁用无条件删除;EVENT需关闭SQL_LOG_BIN并加错误防护,推荐cron+shell限流删除,注意MySQL与应用时区一致。

DELETE 语句加 WHERE 条件必须带时间字段索引
没索引的 WHERE created_at 在百万级表上可能锁表几十秒,甚至拖垮线上查询。MySQL 默认不走索引如果字段类型和比较值不一致——比如 <code>created_at 是 DATETIME,但你写成 WHERE DATE(created_at) ,函数包裹直接让索引失效。
- 先确认索引存在:
SHOW INDEX FROM error_log WHERE Key_name = 'idx_created_at'; - 建索引(如果缺失):
CREATE INDEX idx_created_at ON error_log (created_at); - WHERE 中避免对时间字段用函数:
WHERE created_at ✅,<code>WHERE DATE(created_at) ❌ - 如果表有分区(如按月分区),优先用
ALTER TABLE error_log DROP PARTITION p_2023_12;,比 DELETE 快十倍且不写 binlog
用 LIMIT 分批删,别一次性 DELETE 几十万行
大删不加 LIMIT 容易触发 long transaction、主从延迟飙升、binlog 爆满,还可能被 DBA 监控系统自动 kill。
- 单次最多删 5000–10000 行:
DELETE FROM error_log WHERE created_at - 用循环脚本控制节奏:每次删完
SLEEP(0.1)(MySQL 5.7+ 支持SELECT SLEEP(0.1);),避免冲击 I/O - 检查影响行数:
SELECT ROW_COUNT();,返回 0 就说明删完了,可退出循环 - 别依赖
AUTO_INCREMENT值判断进度——日志表通常不靠主键顺序
清理前务必备份匹配的数据快照
删错时间条件,比如把 写成 <code>>,或者时区理解偏差(数据库用 UTC,你按本地时间算),几秒钟就不可逆。
- 先跑一次
SELECT COUNT(*) FROM error_log WHERE created_at 看量级 - 导出样本验证:
SELECT * FROM error_log WHERE created_at ,确认最“新”的那批确实是该删的 - 导出全量匹配数据到文件(哪怕只保留 1 天):
mysqldump -u user -p db error_log --where="created_at error_log_2023_old.sql - 生产环境禁止用
DELETE * FROM或不带 WHERE 的 DELETE —— 没人能救回没条件的删操作
用事件调度器(EVENT)自动执行,但得关掉 SQL_LOG_BIN
MySQL 的 EVENT 能定时跑,但默认会写 binlog,主库删了,从库也删,看起来没问题——可一旦某次删失败卡住,EVENT 不报错也不重试,日志就悄悄越积越多。
- 启用 EVENT:
SET GLOBAL event_scheduler = ON; - 创建事件时显式关 binlog:
SET SQL_LOG_BIN = 0;,否则主从同步压力大,还可能因 binlog format 限制报错 - 事件体里必须包含错误防护:
IF (SELECT COUNT(*) FROM error_log WHERE created_at 0 THEN ... - 更稳的做法是用外部 cron + shell 脚本调
mysql -e "DELETE ... LIMIT 5000",失败可发钉钉告警,日志也更好查
实际执行时,最容易被忽略的是时区一致性——应用写日志用 Asia/Shanghai,MySQL server time_zone 是 +00:00,NOW() 和业务时间差 8 小时,删着删着就把昨天的日志干掉了。









