核心原因是eloquent全量加载模型+缓存累积导致内存线性/指数增长;应改用db::table()->cursor()流式查询、chunkbyid分块、禁用全局作用域与事件、显式选字段,并结合gc_collect_cycles()和队列分段执行。

命令行执行 Laravel 任务时内存溢出,核心原因是 Eloquent 默认把整批数据加载为模型对象,加上查询缓存、全局作用域、关联预加载等叠加,内存占用呈线性甚至指数增长。解决关键不是调高 memory_limit,而是从数据获取方式、模型开销、执行环境三方面切断内存膨胀链。
改用原生查询或无模型遍历
避免 Model::query()->get() 这类全量加载。能不用 Eloquent 就不用:
- 纯读取导出类任务,直接用 DB::table('users')->select(...)->cursor(),逐行返回数组,内存恒定在几 MB
- 需要简单处理但不依赖访问器/类型转换,用 DB::table()->chunkById(1000, function ($rows) { ... }),比 Eloquent chunkById 内存低 40% 以上
- 统计或聚合场景,根本别查数据,DB::table()->selectRaw('COUNT(*), AVG(score)')->first()
关闭 Eloquent 非必要特性
即使用了 Model,也要剥离内存大户:
- 加 ->withoutGlobalScopes() 关闭软删除、租户等全局作用域,防止静态缓存累积
- 禁用模型事件监听:->withoutEvents(),避免事件对象驻留
- 显式指定字段:->select('id', 'name', 'email'),不查 text/blob 大字段和未索引字段
- 彻底绕过模型实例化:User::select('id', 'name')->get(['id', 'name']) 返回数组而非对象(Laravel 10+ 支持)
分段 + 流式 + 异步组合落地
单次命令行脚本不是万能解法,要拆解执行路径:
- 用 chunkById(500) 分块,每块处理完立即 gc_collect_cycles() 和 Model::flushEventListeners(); Model::clearBootedModels()
- 导出 CSV 等文件,用 response()->stream() 思路反向操作:打开文件句柄,每块写入后 fwrite() + fflush(),不拼大字符串
- 超 10 万行任务,改用队列驱动:Artisan::queue('process:batch', ['start_id' => 1, 'end_id' => 500]),每条队列只处理固定范围,天然隔离内存
- 开发阶段加 ini_set('memory_limit', '256M') 只为调试,上线必须删掉——靠配置硬扛说明方案没到位
验证与监控手段
别猜,要测:
- 在循环开头加 echo memory_get_usage() / 1024 / 1024 . " MB\n";,确认是否稳定不涨
- 用 php -d memory_limit=64M artisan your:command 主动压测,提前暴露问题
- 检查 MySQL 的 wait_timeout 是否太短(建议 ≥ 300),避免 chunk 中间断连报 “MySQL server has gone away”
- 用 DB::enableQueryLog()(仅开发)看是否意外触发 N+1 查询











