删除数据前必须先用SELECT验证WHERE条件:检查行数是否合理、最小/最大日期是否在预期范围、抽样内容是否正确,并确认TIMESTAMP字段用的是created_at还是updated_at。

WHERE 中用 DATE_SUB(NOW(), INTERVAL 1 YEAR) 做日期比较最稳妥
直接用 WHERE date_col 是删除一年前数据的推荐写法,它明确、可索引、时区行为可控。别用字符串拼接或 <code>DATE_ADD 反向写,容易出错且难维护。
常见错误是写成 WHERE date_col —— 这种硬编码日期下线后就失效;或者用 <code>WHERE YEAR(date_col) ,会强制全表扫描,哪怕 <code>date_col 有索引也用不上。
-
DATE_SUB(NOW(), INTERVAL 1 YEAR)返回的是带时间的 datetime,和 datetime/timestamp 字段类型一致,能走索引 - 如果字段是
DATE类型(无时分秒),MySQL 会自动做隐式类型转换,不影响索引使用,但建议统一用DATE_SUB(CURDATE(), INTERVAL 1 YEAR)更语义清晰 - 注意时区:确保 MySQL 服务端时区(
SELECT @@time_zone)和业务预期一致,否则NOW()可能比应用层时间早/晚几小时
DELETE 语句必须加 LIMIT 防误删,尤其首次执行
哪怕加了 WHERE,也别信“逻辑肯定对”。历史数据量大时,一次删太多还可能锁表或触发 binlog 写入压力。先试删 10 条,确认 WHERE 条件命中的是目标数据,再逐步放大。
示例安全操作流程:
DELETE FROM orders WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR) LIMIT 10;
查一下是否真删了旧数据:
SELECT MIN(created_at), MAX(created_at) FROM orders LIMIT 1;
- 确认无误后,可分批删:每次
LIMIT 10000,间隔几秒再执行下一批 - 避免在高峰期执行;如表有主从复制,注意
binlog_format=ROW下大事务可能造成从库延迟 - 别用
DELETE * FROM ...—— MySQL 不支持这种语法,会报错ERROR 1064
WHERE 里别对日期字段用函数,否则索引失效
这是最容易踩的坑:WHERE YEAR(created_at) 或 <code>WHERE DATE(created_at) 看似直观,但会让 MySQL 放弃使用 <code>created_at 上的索引,转为全表扫描。
真正高效的方式,是把计算移到右边,让左边保持裸字段:
- ✅ 正确(索引有效):
WHERE created_at 或 <code>WHERE created_at - ❌ 错误(索引失效):
WHERE DATE(created_at) 、<code>WHERE YEAR(created_at) = 2022、WHERE created_at + INTERVAL 1 DAY > NOW() - 如果业务确实需要按年/月聚合筛选,考虑建生成列(generated column)+ 索引,而不是运行时算
用 SELECT 验证 WHERE 条件再 DELETE,永远不嫌多
删数据前不查,等于蒙眼开车。哪怕脚本里写了 WHERE,也要单独跑一遍 SELECT 看结果集是否符合预期。
重点检查三件事:
- 返回行数是否合理?比如预期删 5 万条,但
SELECT COUNT(*)返回 500 万,说明条件太宽 - 最小/最大日期是否在预期范围内?
SELECT MIN(created_at), MAX(created_at) FROM table WHERE ... - 抽样几条看内容:
SELECT id, created_at, status FROM table WHERE ... ORDER BY created_at LIMIT 5
特别注意:如果字段是 TIMESTAMP 类型且设了 ON UPDATE CURRENT_TIMESTAMP,created_at 和 updated_at 可能不同,务必确认 WHERE 用的是创建时间还是更新时间——删错字段比删错时间更难回滚。


















