延迟任务必须用dispatch()->delay(),不能用schedule()或sleep();delay()只接受整数秒,传DateTime对象会转为0导致立即执行;正确做法是now()->addMinutes(5)->diffInSeconds();sync驱动忽略delay(),须用redis等支持延迟的驱动;redis需配置retry_after大于最大延迟;事务中发延迟任务应使用DB::afterCommit()。

延迟任务必须用 dispatch()->delay(),不是 schedule(),也不是 sleep()。用错地方会导致任务立刻执行、堆积不触发,或直接阻塞请求。
delay() 只接受整数秒,别传 Carbon 或 DateTime 对象
很多人写 SendEmail::dispatch()->delay(now()->addMinutes(5)),结果任务马上执行——因为 delay() 底层只认 int,传对象会被转成 0。
- 正确做法是显式算秒数:
now()->addMinutes(5)->diffInSeconds()(推荐,时区安全) - 或者硬写
300,但别用strtotime('now + 5 minutes'),容易因服务器时区和 PHP 默认时区不一致出错 -
later()是别名,行为完全等同于delay(),别以为它能接受时间对象
队列驱动必须支持延迟,sync 驱动会直接忽略 delay()
QUEUE_CONNECTION=sync 时,delay() 调用不会报错,但也不会延迟——任务立刻同步执行,完全失效。
- 必须设为
redis、database或beanstalkd - 用
redis时,检查config/queue.php中对应连接的retry_after:它得大于你最长延迟(比如最大延 24 小时,就设86400),否则任务可能被误标失败并重入队列 - 用
database驱动要先跑php artisan queue:table && php artisan migrate
事务里发延迟任务,得用 DB::afterCommit()
在数据库事务中调 dispatch()->delay(),如果事务回滚,任务仍可能已入队并后续执行——这不是你想要的“仅提交后才发”。
- Laravel 9+ 正确写法:
DB::afterCommit(fn () => SendNotification::dispatch($user)->delay(now()->addMinutes(1))) - 这个闭包只在 commit 成功后运行;rollback 发生时,闭包根本不会执行
- 别自己用 try/catch + 手动判断事务状态,
afterCommit()已封装好底层逻辑
Supervisor 日志看不到延迟任务?它根本还没到执行时间
延迟任务提交后立即返回,不会阻塞,也不会立刻出现在 ready 队列里。你在 Redis 中看到的是 queues:default:delayed 的 ZSET,只有到期了才会被 queue:work 拉过去。
- 别盯着 Supervisor 日志等“新任务进来”,它只记录 ready 状态任务的执行
- 查延迟任务是否入队成功,看 Redis 的
ZCARD queues:default:delayed或 database 表jobs中reserved_at IS NULL AND available_at > now() - 确保
php artisan queue:work进程常驻运行,且没加--once


















