yii2内存泄漏主因是日志缓冲区堆积、ar循环引用及batchinsert资源未释放,需调小flushinterval、关闭tracelevel、剥离ar行为、手动flush和gc_collect_cycles。

Yii2日志缓冲区堆积导致内存线性上涨
脚本循环几万次后报 Allowed memory size of 134217728 bytes exhausted,大概率不是PHP内存限制太小,而是Yii2日志在内存里越攒越多。默认flushInterval是1000,每满1000条才刷一次盘;更致命的是,traceLevel > 0时每次log()都调用debug_backtrace(),每个日志项附带完整调用栈,内存直接按行数线性增长。
实操建议:
- CLI脚本开头立刻设
Yii::getLogger()->flushInterval = 1,强制每次log都落盘 - 关闭调试痕迹:
'traceLevel' => 0(配置中)或运行时Yii::getLogger()->traceLevel = 0 - 非调试阶段可彻底禁用:
'enableLogging' => false,但记得提前把错误日志重定向到文件 - 每批操作后手动
Yii::getLogger()->flush(),避免最后一批日志丢失
ActiveRecord实例未释放引发隐式内存泄漏
User::findOne()或new User()看着轻量,实际构造时会自动挂载behaviors、事件监听器、属性变更追踪器——这些对象之间存在循环引用(比如TimestampBehavior把model当$owner存着),PHP垃圾回收器无法清理。
实操建议:
- 批量场景下彻底避开ActiveRecord:改用
Yii::$app->db->createCommand()->queryAll()或原生PDO查数组 - 必须用AR时,显式剥离行为:
$model->detachBehaviors(),再unset($model) - 检查model类的
behaviors()方法,删掉非必要的behavior,尤其TimestampBehavior、BlameableBehavior这类自带状态管理的 - 避免在循环内反复
new模型,哪怕只读——构造开销本身就有累积效应
batchInsert执行后资源不释放
createCommand()->batchInsert()->execute()跑完,框架内部还留着SQL解析上下文、参数绑定对象、日志缓存等中间态,不会自动触发GC。拆成1000条一批,内存可能涨到300MB以上且不回落。
实操建议:
- 每批
execute()后立即调用gc_collect_cycles()(PHP 7.3+效果更明显) - 把大batch拆小:从1000降到100或50,并在每批后
Yii::getLogger()->flush() - 极端情况绕过框架:用
Yii::$app->db->pdo->prepare($sql)->execute($params)手写插入,完全跳过AR和QueryBuilder层 - 确认
memory_get_usage(true)是否持续增长——如果是,说明底层资源没释放,不是单纯变量没unset
CLI环境OOM静默退出难排查
Web环境OOM会抛Fatal error并记录日志;CLI下若没设ini_set('memory_limit', '-1'),实际受系统ulimit和PHP编译默认值约束,OOM时Linux OOM killer直接发SIGKILL,进程瞬间消失,无日志、无堆栈、register_shutdown_function都不执行。
实操建议:
- 脚本开头第一行加
ini_set('memory_limit', '2G')(或'-1'),让问题暴露为明确报错而非静默崩溃 - 用
memory_get_usage(true)打点监控,比如每1000次循环输出一次,定位暴涨拐点 - 别依赖
unset()或赋null——AR对象内部引用链不断,这些操作基本无效 - 真正要盯的是
gc_collect_cycles()返回值和memory_get_usage()差值,而不是“代码看着已经清理了”











