查 failed_jobs 表不能直接 new failedjob 模型,因 laravel 10+ 默认禁用其写操作,该模型无软删除、无 casts 和 accessor,无法正确解析 payload 与 exception;应使用 db::table('failed_jobs') 查询,重点分析 exception(堆栈)、payload(任务类名与参数)、failed_at(时间戳)三字段。

查 failed_jobs 表不能只看 exception 字段,真正要定位根因,得结合 payload 里的任务上下文、failed_at 时间点、以及重试时实际反序列化的行为——否则容易在 vendor 堆栈里兜圈子。
查 failed_jobs 表为什么不能直接 new FailedJob 模型?
Laravel 10+ 默认禁用 Illuminate\Queue\Failed\FailedJob 的写操作,它没有构造函数保护、不支持软删除、也没有常规的 $casts 或 accessor。模型实例化后读不到 payload 和 exception 的结构化解析结果,反而容易触发自动类型转换或 JSON 解码失败。
更稳的做法是走查询构建器:
DB::table('failed_jobs')->orderByDesc('failed_at')->limit(10)->get()- 如果改过表名(比如配置了
QUEUE_FAILED_TABLE=jobs_failed),记得把'failed_jobs'换成对应值 - 字段中重点盯三个:
exception(原始堆栈)、payload(含任务类名、参数、尝试次数)、failed_at(时间戳,可对齐日志)
exception 堆栈太长,怎么快速抓到关键错误行?
几百行堆栈里真正有用的就两处:最后一段报错 + 最靠近 at app/ 的那一行。vendor 路径下的调用只是中转,不是源头。
实操建议:
- 用
json_decode($job->payload, true)['data']['command']看任务类全名,确认是不是已删或命名空间改过 - 在
exception字符串里搜Call to a member function,大概率是构造函数里存了 null 对象(比如auth()->user()或未初始化的 Eloquent 关系) - 搜
PDOException或Connection refused,说明执行时数据库连不上——但记录仍能写进failed_jobs,因为 Laravel 是先 try 再 fail - 如果堆栈里反复出现
unserialize()相关错误,检查任务类是否用了不可序列化的资源(如Request、闭包、数据库连接)
重试单条失败任务,为什么又失败?
php artisan queue:retry {id} 只是把原 payload 重新推入队列,不会读取 reason、不会跳过构造函数、也不会重置依赖状态。
常见翻车点:
- 构造函数里写了
public function __construct(Request $request)——Request无法序列化,重试时变成空对象,handle 里一访问就报Trying to get property 'xxx' of non-object -
$this->user = auth()->user()写在构造函数里,重试时 session 已失效,$this->user是null - 任务类被删了或命名空间改了,重试时报
Class xxx does not exist,因为 payload 里存的是字符串类名,不是实时反射 - 想看重试时到底跑哪段代码?加
--pretend:php artisan queue:retry 123 --pretend,它会打印反序列化后的类和handle()调用链
人工补标失败原因后,怎么让重试带上 context?
默认 queue:retry 完全不碰 failed_jobs.reason 字段,它只读 payload。所以后台填了 reason,重试日志里还是看不到。
正确做法是写个自定义命令,在重试前把 reason 注入 payload:
- 手动改
payload:用json_decode($job->payload, true)拿数组,塞进['data']['manual_reason'] = $job->reason,再json_encode回去 - 用
Queue::pushRaw()把新 payload 推入队列,而不是依赖原生命令 - 别用 Eloquent update
failed_jobs表——payload和exception是 JSON 字符串,Eloquent 的 casts 和自动时间戳可能破坏结构 - 后台编辑页必须用
DB::table('failed_jobs')->where('id', $id)->update(...),绕过模型副作用
最常被忽略的一点:payload 里存的是任务类的完整命名空间和构造参数,但没存 Laravel 容器状态。重试本质是“新进程里 new 一个对象再 call handle”,不是“接着上次断点跑”。这个边界不厘清,所有重试都像在赌运气。











