PHP批量插入数据库的循环存在内存泄漏,求定位泄漏位置
PHP+Doctrine DBAL分批插入数据内存泄漏问题排查与解决
核心泄漏原因
- Doctrine SQL日志缓存:这是最高概率的泄漏点。Doctrine DBAL 2.x默认开启的SQL日志(尤其是调试模式下的
DebugStack实现)会记录每一次执行的SQL语句、绑定参数、执行耗时。你每次绑定的jsonstr大小在63MB左右,每执行一次日志就多存63MB数据,且不会自动清空,循环多次后内存占用会快速攀升。 - Doctrine ORM UnitOfWork 持有实体引用:从日志的
UoW size: 980243可以看出,UnitOfWork已经持有了近百万个实体对象,这部分直接占了初始的5.9GB内存,且默认不会自动释放,后续循环的增量内存叠加后很容易触达内存上限。 - 循环内大变量未主动释放:每次循环生成的
$batch(5万条ScheduleItem对象数组)、$jsonstr(63MB左右的字符串)都是大内存变量,PHP7.4的自动GC触发阈值较高,循环执行速度快的情况下GC来不及回收旧变量,会导致内存堆积。 - 全量数据数组长期持有:
$this->intermediateSchedule持有全量的ScheduleItem对象数组,全程占用内存不会释放,进一步加大了内存压力。
修复方案
- 关闭Doctrine SQL日志,彻底避免参数缓存占用,循环执行前添加代码:
$this->insertStatement->getConnection()->getConfiguration()->setSQLLogger(null);
- 每次处理完批次后清空UnitOfWork(用了ORM的情况下必须加):
// 放在循环处理逻辑之后 $entityManager->clear();
- 每次循环末尾主动释放当前批次的大变量,强制回收内存:
// 放在循环最后一行 unset($batch, $jsonstr, $start); gc_collect_cycles(); // 手动触发GC回收废弃内存
- 优化全量数组的内存占用,如果后续不需要复用
$this->intermediateSchedule,可以用array_splice代替array_slice,处理完的批次直接从原数组删除,逐步降低数组内存占用:
// 替换原来的for循环逻辑,每次取前5万条,处理完直接移除 while (!empty($this->intermediateSchedule)) { $batch = array_values(array_splice($this->intermediateSchedule, 0, $FBS)); // 原有处理逻辑不变 }
- 检查
ScheduleItem类的jsonSerialize方法实现,确保方法内没有生成全局引用、没有遗留未释放的大变量。
内容的提问来源于stack exchange,提问作者olidem
相关产品推荐
相关产品推荐

