Cron表达式实战:Cron调试为什么我的定时任务没执行

Cron 表达式写好了,任务就是不执行。日志没有报错,手动跑脚本没问题,但定时任务就是不动。这种情况大概率是 Cron 表达式或环境配置的问题。本文整理了 8 大常见原因和排查方法。

原因一:时区不一致

服务器时区是 UTC,但你的 Cron 表达式按北京时间写的。结果"每天 9 点执行"变成了北京时间 17 点执行。

# 查看服务器时区
$ timedatectl | grep "Time zone"
Time zone: UTC (UTC, +0000)

# 查看当前时间
$ date
Mon Aug 19 06:30:00 UTC 2026  # UTC 6:30 = 北京时间 14:30

排查方法:在脚本第一行打印时间,确认实际执行时间:

#!/bin/bash
echo "脚本执行时间: $(TZ='Asia/Shanghai' date)"

解决方案:

  • 服务器时区改为 Asia/Shanghai
  • 或在 crontab 中指定时区(部分 cron 实现 支持 CRON_TZ=Asia/Shanghai
  • Docker 容器中设置 TZ=Asia/Shanghai 环境变量

Spring Boot 设置时区:

@Scheduled(cron = "0 0 9 * * ?", zone = "Asia/Shanghai")
public void execute() { }

原因二:日和周字段同时指定

Quartz 和 Spring 要求日和周字段不能同时为 *,必须有一个用 ?

// 错误 — 日和周同时为 *,Quartz/Spring 会报错
@Scheduled(cron = "0 0 0 * * *")

// 正确 — 周字段用 ?
@Scheduled(cron = "0 0 0 * * ?")

如果你写的是 0 0 0 15 * MON(每月 15 号且是周一),这种"且"的语义在 Cron 中不支持。日和周同时指定具体值时,是"或"的关系——15 号或周一都会执行。

原因三:07 的周日歧义

# crontab — 0 表示周日
0 0 * * 0      # 每周日执行

# Quartz — 1 表示周日,0 是无效值
# 0 0 0 ? * 0  # 错误!Quartz 周字段范围是 1-7

# Spring — 0 和 7 都表示周日
@Scheduled(cron = "0 0 0 ? * 0")   # 正确,周日
@Scheduled(cron = "0 0 0 ? * 7")   # 正确,周日

排查方法:确认你用的框架的周字段编码。用英文缩写 SUNMON 可以彻底避免这个问题。

原因四:/ 步长的起始值陷阱

*/5 看起来是"每 5 分钟",但 5/15 不一定是"从 5 开始每 15 分钟"——取决于实现。

# 正确 — 从 0 开始每 5 分钟
*/5 * * * *

# 从第 5 分钟开始每 15 分钟 → 5, 20, 35, 50
5/15 * * * *

# 常见误解:以为 */5 等价于 1/5
# 实际 */5 = 0/5 = 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55

在 Quartz 中,1/3 表示从第 1 秒开始每 3 秒:1, 4, 7, 10, …。如果你期望的是"从第 0 秒开始每 3 秒"(0, 3, 6, 9…),应该写 0/3*/3

原因五:月末日期不存在

# 2 月没有 30 号和 31 号
0 0 0 30 * *    # 2 月不执行!
0 0 0 31 * *    # 2、4、6、9、11 月不执行

0 0 0 30 * * 在 2 月不会执行,因为 2 月最多 28/29 天。如果需要月末执行,用 L

// Spring/Quartz
@Scheduled(cron = "0 0 0 L * ?")   // 每月最后一天,自动适配

Unix crontab 不支持 L,只能用脚本判断:

0 0 0 28-31 * * [ "$(date -d '+1 day' +%d)" = "01" ] && /path/to/script.sh

原因六:crontab 用户权限

crontab 分用户级别和系统级别:

# 用户级 crontab — 当前用户执行
$ crontab -e
0 2 * * * /home/user/backup.sh

# 系统级 crontab — root 执行
# /etc/crontab 或 /etc/cron.d/
0 2 * * * root /opt/scripts/backup.sh

用户级 crontab 的脚本以当前用户权限执行。如果脚本需要 root 权限(如操作 /var/log),必须放在系统级 crontab 中,或用 sudo 配置免密执行。

排查方法:

# 查看 crontab 日志
$ grep CRON /var/log/syslog

# 查看特定用户的 crontab
$ sudo crontab -u root -l
$ sudo crontab -u www-data -l

原因七:环境变量缺失

crontab 执行的环境与 shell 不同。你在终端里能跑的脚本,在 crontab 里可能因为找不到命令而失败。

# crontab 的 PATH 很精简
# 通常只有 /usr/bin:/bin
# 你安装的 node/python/go 可能不在 PATH 中

排查方法:在脚本开头显式设置 PATH:

#!/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin:/opt/node/bin
export PATH

# 或者用绝对路径
/opt/node/bin/node /opt/scripts/cronjob.js

也可以在 crontab 文件顶部设置:

PATH=/usr/local/bin:/usr/bin:/bin:/opt/node/bin
0 2 * * * /opt/scripts/backup.sh

原因八:秒字段遗漏

从 Unix crontab 迁移到 Spring/Quartz 时,容易忘记补秒字段:

# Unix crontab — 5 段,正确
30 9 * * 1-5
// Spring — 直接复制,6 段格式少一段
@Scheduled(cron = "30 9 * * 1-5")  // 错误!Spring 需要 6 段

// 正确 — 前面补秒
@Scheduled(cron = "0 30 9 * * ?")

少一段会导致 Spring 启动时报 IllegalArgumentException,错误信息提示字段数不匹配。

排查清单

排查项检查方法
时区脚本内打印 date,对比预期时间
日周互斥确认 Quartz/Spring 中日和周有一个是 ?
周编码SUN/MON 缩写替代数字
步长起始*/5 从 0 开始,不是 1
月末日期2 月没有 30/31 号,用 L 或脚本判断
用户权限grep CRON /var/log/syslog 查看执行日志
环境变量脚本开头设置 PATH,或用绝对路径
字段数Spring/Quartz 是 6 段,Unix 是 5 段

在线调试

排查问题时,需要反复验证 Cron 表达式。可以在 盘子工具站 Cron 表达式生成器 上做这种验证——输入表达式后,校验功能会指出语法错误和出错字段,比如 0 0 0 * * * 会提示"日和周不能同时为*“。中文解释帮你确认语义是否正确——如果解释显示"每天执行"但你期望的是"工作日执行”,说明表达式写错了。触发时间预览最实用——输入 0 0 0 30 * ? 后看触发时间列表,2 月的日期直接消失,一眼就能发现问题。
在这里插入图片描述
在这里插入图片描述
建议把上面排查清单里的每个错误示例粘贴到工具里,观察工具的校验提示和中文解释,加深对各种坑的理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值