在队列任务中应使用 db::table()->upsert() 持久化进度到数据库,依赖 $this->job->uuid() 作为唯一键,避免模型事件、事务干扰及构造函数操作;前端轮询需查库并加索引,缓存仅作可选加速。

队列任务里怎么存进度到数据库
直接在任务类里用 Eloquent 更新记录就行,但得避开模型事件和事务干扰。Laravel 队列默认不开启 DB 事务,但如果你手动开了(比如在 job handle() 里用了 DB::transaction),更新进度可能被回滚。
-
dispatch()时传入一个唯一$jobId或用$this->job->uuid()生成标识,避免多个相同任务互相覆盖 - 别在
__construct()里查或改模型——构造阶段模型状态不可靠,且部分驱动(如 Redis)会序列化对象,Eloquent 模型容易出错 - 用
DB::table('job_progress')->upsert()替代先查后 save,避免并发写冲突;字段至少包含job_uuid、progress、updated_at
Laravel 10+ 的 InteractsWithQueue 怎么配合进度更新
这个 trait 提供了 $this->job 实例,里面能拿到 uuid() 和当前尝试次数,是天然的进度锚点。但注意:它只在 handle() 执行时才完整可用,不能在 failed() 或构造函数里依赖它。
-
$this->job->uuid()是最稳的 key,比自定义 ID 更可靠(尤其用 Horizon 时) - 如果任务分多步(比如处理 1000 条数据),每处理 100 条就调一次
DB::table(...)->where('job_uuid', $this->job->uuid())->update(...),别等全做完再写 - 别在
failed()方法里重置进度为 0——用户可能重试,该保留上次成功进度
为什么用缓存存进度反而容易丢数据
Redis 缓存快,但队列失败重试、机器重启、TTL 过期都会让进度消失。生产环境要持久化,就得落库,缓存只适合做临时高频读(比如前端轮询)。
- 不要用
Cache::put('progress_'.$uuid, $p, 3600)当主存储——没事务保证,也没历史可查 - 如果真要用缓存加速读,写库后立刻
Cache::forever('progress_'.$uuid, $p),但读的时候必须 fallback 到 DB 查询 - Redis 持久化开关(RDB/AOF)不是保险柜,队列进程崩溃时最后几次 update 可能根本没刷到磁盘
前端轮询 /api/progress/{uuid} 接口要注意什么
这个接口本质是查数据库,不是查内存或缓存,所以必须加索引,否则并发一高就拖慢整个队列消费。
- 给
job_progress.job_uuid加唯一索引或普通索引,避免全表扫描 - 返回结构保持极简:
{"progress": 65, "status": "processing"},别塞模型关系或额外字段 - 别在接口里触发重试逻辑或写操作——纯读,否则和队列写进度形成竞争条件
真正难的不是存百分比,而是保证「写进度」和「任务执行流」在各种失败场景下不脱节。比如任务卡住、超时 kill、Horizon 手动停止,这些时候进度值是否还可信,得靠 uuid + 时间戳 + 状态字段组合判断,单靠一个数字撑不住。











