DELETE不能直接放入crontab,因环境缺失、%被误解析、密码泄露、无错误处理及缺少预检回滚机制;需转义%、用login-path、加日志与行数校验、备份先行。

为什么 DELETE 不能直接丢进 crontab 里跑
因为 crontab 默认不加载用户环境,mysql 命令可能找不到,密码明文写在命令里会留痕,且没错误处理时删错数据根本没人知道。
更麻烦的是:SQL 语句里如果含 %(比如 DATE_SUB(NOW(), INTERVAL 1 DAY)),crontab 会把它当转义符吃掉,导致语法错误或时间计算错乱。
- 必须用
\%转义所有%字符 - 数据库连接要显式指定
--defaults-file或用环境变量,避免密码暴露在ps输出里 - 加
-e参数让mysql在出错时立刻退出,配合 shell 的set -e控制流程 - 建议先用
SELECT COUNT(*)预估影响行数,再决定是否执行DELETE
mysqldump 备份 + DELETE 之前必做的三件事
自动删数据不是“删完就完”,而是“删得明白、能回滚、有记录”。很多人跳过这步,结果半夜删掉三个月订单,凌晨三点翻备份。
- 用
mysqldump --where="created_at 导出待删数据,存到带时间戳的文件里(如 <code>/backup/delete_orders_$(date +\%Y\%m\%d_\%H\%M).sql) - 把
DELETE改成SELECT COUNT(*)先跑一遍,输出结果重定向到日志,确认数量合理 - 在 SQL 脚本开头加
SET SQL_LOG_BIN = 0;(仅限 binlog 不用于恢复的场景),避免主从延迟雪上加霜
写一个可被 crontab 安全调用的 shell 脚本
脚本不是越短越好,而是越“傻瓜越稳”——它得自己判断能不能跑、删多少、出错了往哪写日志。
- 开头固定写
#!/bin/bash和set -e,任何命令失败立即终止 - 用
mysql_config_editor set --login-path=prod --user=cleaner --password配好登录路径,脚本里只写mysql --login-path=prod - 删除语句包在
if [ $(mysql --login-path=prod -Nse "SELECT COUNT(*) FROM logs WHERE ts 里,防误删空表 - 每步操作后用
echo "$(date): ..." >> /var/log/cleaner.log记日志,别依赖 stdout
#!/bin/bash set -e LOG="/var/log/cleaner.log" echo "$(date): start" >> $LOG COUNT=$(mysql --login-path=prod -Nse "SELECT COUNT(*) FROM events WHERE created < DATE_SUB(NOW(), INTERVAL 14 DAY)") if [ "$COUNT" -gt 50000 ]; then mysql --login-path=prod -e "DELETE FROM events WHERE created < DATE_SUB(NOW(), INTERVAL 14 DAY) LIMIT 10000" echo "$(date): deleted 10000 rows" >> $LOG else echo "$(date): skip — only $COUNT rows found" >> $LOG fi
crontab 条目里最容易漏掉的四个反斜杠
写完脚本,crontab -e 里一行配错,整个任务静默失效。最常栽在 %、空格、路径、环境变量上。
- 所有
%必须写成\%,包括DATE_SUB(NOW(), INTERVAL 1 \%这种 - 脚本路径写绝对路径,
/home/user/bin/clean_events.sh,别用~/bin/... - 加
SHELL=/bin/bash和PATH=/usr/local/bin:/usr/bin:/bin到 crontab 文件顶部,否则找不到mysql - 用
2>&1 >> /var/log/cleaner_cron.log把 stderr 也捕获,不然报错你根本看不见
真正难的不是写脚本,是每次改完都手动跑一遍 /home/user/bin/clean_events.sh,再查日志、看 MySQL 的 SHOW PROCESSLIST,确认它真在删、没卡住、没锁表。自动化最怕“以为它在动”,其实早就停在某条报错上了。

















