Laravel队列内存爆满主因是长生命周期进程+未控内存增长,应通过--memory参数让Worker自动重启、任务内手动检测释放、DB查询改用chunk/聚合函数、避免静态缓存及未释放资源来解决。

队列任务跑着跑着内存爆了怎么办
直接结论:Laravel 队列任务默认不会自动释放内存,尤其在循环处理大量数据、加载大模型或反复 new 对象时,memory_get_usage() 会持续上涨,最终触发 Allowed memory size exhausted。这不是 Laravel 的 bug,而是 PHP 运行机制 + 队列进程长生命周期共同导致的。
核心对策不是调高 memory_limit,而是主动控制单个任务的内存边界和生命周期:
- 用
--memory=128启动php artisan queue:work,让 Worker 在接近阈值时自动重启(注意:这是硬性 kill,未完成任务会重试) - 在任务
handle()内部手动检测:if (memory_get_usage() > 100 * 1024 * 1024) { return; },配合release()或日志告警 - 避免在任务中复用全局静态变量、单例容器实例或未 unset 的大数组;Laravel 的
app()容器本身不泄漏,但你往里塞了DB::table('huge_table')->get()就另当别论
Laravel 队列任务里 DB 查询怎么防内存炸掉
最常见泄漏源:用 DB::table()->get() 或 Model::all() 一次性拉几万条记录——这些对象全留在内存里,PHP 垃圾回收(GC)在长运行进程中并不积极触发。
正确做法取决于场景:
- 要遍历处理?用
DB::table()->chunk(500, function ($rows) { ... }),每次只载入一批,执行完自动释放 - 要用 Eloquent 模型且需触发事件?改用
Model::query()->chunkById(500, function ($models) { ... }),比chunk()更稳定(避免 offset 越界和重复) - 只是统计或聚合?根本别
get(),直接sum()、count()、pluck('id')等,结果集不实例化模型 - 如果必须全量加载(极少见),处理完立刻
unset($models),并调用gc_collect_cycles()强制 GC(效果有限,仅作补救)
Supervisor 管理 Laravel 队列时,--memory 参数到底管不管用
管用,但容易误解:它不是给每个任务设限,而是给整个 queue:work 进程设内存上限。一旦该进程 RSS 内存超过设定值(如 128MB),Supervisor 会杀掉它并拉起新进程。
这意味着:
-
--memory=128和 php.ini 的memory_limit=256M是两回事:前者是 Supervisor 的监控阈值,后者是 PHP 解释器单次脚本执行上限 - Supervisor 配置里必须启用
autorestart=true和合理startsecs,否则进程被杀后不会自愈 - 不要设得太低(如 64MB):Laravel 框架初始化就占 20–30MB,留余量太少会导致频繁重启,反而影响吞吐
- 配合
--tries=1使用更稳妥,避免因重启导致任务重复执行(尤其非幂等操作)
为什么用 Horizon 还会内存泄漏
Horizon 是调度层,不改变底层 PHP 进程行为。它甚至可能掩盖问题:因为 Horizon 自带进程管理、失败重试、延迟队列等功能,开发者容易忽略任务内部的内存累积。
排查重点在任务代码本身,而非 Horizon 配置:
- 检查是否在
handle()中用了static::$cache = [...]类静态属性缓存,这种不会随任务结束销毁 - 确认没在循环里不断
new HttpClient()或new \SoapClient()—— 这些对象底层可能持有着 C 扩展资源,GC 不一定及时回收 - Horizon 的
trim配置(如horizon.trim.failed)只清理 Redis 中的元数据,和内存无关 - 真要定位泄漏点,用
xdebug_get_memory_usage()或blackfire.io抓一次任务执行前后的内存快照对比,比猜靠谱得多
内存问题从来不在配置开关上,而在你每次 new、get()、load() 的那一行代码里。没做 chunk 的查询、没 unset 的临时数组、没关闭的流资源——它们堆在一起,才让那个“128MB”变得特别脆弱。


















