队列任务中禁用all()遍历全表,应改用chunk(200)分批处理以避免内存溢出;chunk基于主键分页,cursor适用于高并发但要求Laravel 9.19+;慎用dispatch()触发子任务,优先内联逻辑或使用Bus::batch。

队列任务里直接用 foreach 遍历全表会爆内存
别在 handle() 里写 YourModel::all()->each(...) —— 这会把整张表一次性加载进内存,哪怕只有 1 万条记录,也容易触发 PHP 内存限制或拖慢 Worker。Laravel 的 Collection 不是懒加载的,all() 就是全量查、全量载。
- 改用
chunk():按批次拉取,每批处理完自动释放内存 - 避免
get()或all(),尤其在队列任务中 - 如果必须全量处理,加
ini_set('memory_limit', '512M')是治标不治本,且可能影响其他任务
chunk() 和 cursor() 的关键区别
chunk() 基于主键分页(如 id BETWEEN 1 AND 100),稳定但要求主键连续、无删除空洞;cursor() 基于游标(last seen id),适合高并发写入场景,但只支持 Laravel 9.19+ 且不能用在 where 条件复杂时。
-
chunk(100, function ($users) { ... }):安全通用,推荐新项目默认用它 -
cursorPaginate(100)->each(...):需配合cursor()方法,且模型必须有id主键 - 两者都自动跳过已处理的记录,但
cursor()不锁表,chunk()在大表上可能因OFFSET变慢
循环中触发新队列任务要慎用 dispatch()
在 chunk() 回调里对每条数据 dispatch(new ProcessUser($user)),看似解耦,实则可能瞬间压垮 Redis 或数据库队列驱动 —— 1 万条记录 = 1 万个新任务入队,Worker 来不及消费就会堆积。
- 优先合并逻辑:把单条处理逻辑内联到当前任务中,而不是拆成子任务
- 真需要分散执行,用
dispatchAfterResponse()推迟到响应后,或加delay()错峰 - 若必须批量分发,改用
Bus::batch([...])->dispatch(),它自带失败重试和状态跟踪
循环中途出错时,chunk() 默认不会回滚已处理批次
chunk() 是“尽力而为”型遍历:第 5 批失败了,前 4 批已执行的修改不会自动回退。这不是 Bug,而是设计使然 —— 队列任务本身就不保证强事务性。
- 手动加事务:在
chunk()外层包DB::transaction(),但注意不要让事务跨批次(否则锁表太久) - 更稳妥的做法:每个批次自己开事务,失败时记录失败 ID,后续单独重试
- 别依赖
try/catch吞掉异常后继续下一批 —— 静默失败比中断更难排查
实际运行时,最易被忽略的是批次大小与内存/超时的平衡:设太小(如 chunk(10))导致 SQL 请求过多;设太大(如 chunk(5000))又可能超时或 OOM。建议从 chunk(200) 起调,观察日志里的 memory_get_usage() 和 max_execution_time。











