应使用 date_sub(now(), interval 30 day) 而非 now()-86400*30,避免因时区、精度或索引失效导致误删或性能问题。

WHERE 条件里别直接用 NOW() 减固定天数
很多人写 DELETE FROM logs WHERE created_at ,看起来没问题,但实际执行时可能误删——尤其当 <code>created_at 是 DATETIME 类型且数据库时区设置不一致时,NOW() 返回的是系统时区时间,而字段值可能按 UTC 存储。结果就是本该保留的记录被删了,或者该删的没删。
更稳妥的做法是统一转换为 UTC 时间比较:
DELETE FROM logs WHERE created_at <p>如果字段本身就是 <code>TIMESTAMP</code> 类型(自动转时区),那用 <code>NOW()</code> 也行,但得先确认表定义:<code>SHOW COLUMNS FROM logs LIKE 'created_at';</code> 看类型和是否带 <code>DEFAULT CURRENT_TIMESTAMP</code>。</p><h3>删除前必须加 LIMIT 和事务包装</h3><p>历史表动辄几百万行,一次性删光可能锁表、打满 I/O、触发主从延迟。线上环境绝不能裸跑 <code>DELETE</code>。</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/ai/2949" title="Text To Image"><img src="https://img.php.cn/upload/ai_manual/001/246/273/177985764629915.png" alt="Text To Image" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/ai/2949" title="Text To Image" class="overflowclass">Text To Image</a> <p class="overflowclass">一款AI图像与设计工具,主要用于将文本渲染为图片并返回临时本地文件路径,支持可选的 data URI。适用于 Clawhub 或 Codex,用于将纯文本或带样式的文本进行转换,适合需要提升相关任务效率的用户。</p> </div> <a rel="nofollow" href="/ai/2949" title="Text To Image" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>
- 先用
SELECT COUNT(*)估算待删量:SELECT COUNT(*) FROM logs WHERE created_at - 真删时加
LIMIT分批,比如每次 10000 行:DELETE FROM logs WHERE created_at - 在事务里执行,方便出错回滚:
BEGIN; DELETE ...; COMMIT;(注意:MySQL 的DELETE在autocommit=1下默认自动提交,所以显式BEGIN很关键)
索引失效导致全表扫描怎么办
如果 EXPLAIN DELETE FROM logs WHERE created_at 显示 <code>type: ALL,说明没走索引。常见原因有三个:
-
created_at列没建索引 —— 立刻补:CREATE INDEX idx_created_at ON logs(created_at); - 用了函数包装字段,比如
DATE(created_at) —— 改成范围查询:<code>created_at - 字段类型和条件类型不匹配,比如
created_at是DATETIME,但条件传了字符串'2024-01-01'(隐式转换)—— 统一用带时分秒的完整格式或参数化传入
分区表比 DELETE 更适合大批量清理
如果历史数据按月/年归档,且 MySQL 版本 ≥ 5.7,优先考虑按 created_at 做 RANGE 分区。删整个月的数据就变成 ALTER TABLE logs DROP PARTITION p202301;,毫秒级完成,不锁表、不生成大 binlog、不触发 InnoDB purge 队列。
但要注意:分区键必须是主键/唯一键的一部分,否则建表会报错 ERROR 1503 (HY000)。已有表想加分区只能重建,得提前评估停机窗口。
真正麻烦的不是语法,是判断“哪些数据能删”——业务方说“保留 90 天”,但审计要求某些操作日志必须存 7 年。删之前,一定得对上保留策略文档,而不是只看 SQL 跑通了没。










