Symfony4+Doctrine2双向多对多关联引发OutOfMemoryException问题
这种多对多关联引发的内存溢出在Symfony+Doctrine项目里特别常见,尤其是关联条目达到一定数量后,大概率是关联加载策略不合理或者视图中无意识触发大量懒加载查询导致的。给你几个针对性的解决方案:
1. 检查并调整Doctrine关联的加载策略
首先去看你的MapParkingType实体映射(不管是注解、XML还是YAML格式),是不是给多对多关联设置了fetch="EAGER"?如果是的话,每次加载MapParkingType都会直接把所有关联的MapParking实体,以及这些停车场附带的其他关联数据全部拉进内存——50条关联的话,每个停车场再带一堆属性和关联,很容易就把128MB内存撑爆。
解决办法:把关联的加载策略改回默认的LAZY(懒加载),这样只有当你真正访问关联的停车场数据时,Doctrine才会去执行查询,避免一次性加载所有冗余数据。
示例(注解格式):
// 把原来的EAGER加载改成默认的LAZY /** * @ManyToMany(targetEntity="App\Entity\MapParking", mappedBy="types") */ private $parkings;
2. 避免视图中不必要的关联遍历与懒加载触发
检查你的Twig视图(假设用的是Twig),是不是在遍历MapParkingType时,又嵌套遍历了每个类型关联的所有MapParking,甚至还访问了停车场的深层关联?比如:
{% for type in parkingTypes %} <h2>{{ type.name }}</h2> {% for parking in type.parkings %} <p>{{ parking.name }} - {{ parking.address.city.name }}</p> {% endfor %} {% endfor %}
这里每循环一个停车场,访问parking.address.city都会触发一次懒加载查询,而且所有加载出来的实体对象都会驻留在内存里,很快就会导致内存耗尽。
优化方案:
- 用DTO(数据传输对象)或者自定义Repository查询,只查询视图需要的字段,不要加载整个实体。比如在
MapParkingTypeRepository里写一个专门的查询:
public function findAllWithRequiredParkingData() { return $this->createQueryBuilder('t') ->select('t.id, t.name, p.id as parking_id, p.name as parking_name') ->leftJoin('t.parkings', 'p') ->getQuery() ->getResult(); }
这种查询返回的是纯数组,内存占用比实体对象小得多,而且只加载视图需要的数据,不会附带任何冗余关联。
3. 用分页减少单次加载的数据量
如果确实需要展示所有关联数据,考虑用分页工具(比如KnpPaginatorBundle)把数据拆分成多页,每次只加载当前页的MapParkingType和关联数据,避免一次性把所有数据塞进内存。
4. 临时调整内存限制(仅作为应急方案)
如果你只是临时需要绕过这个问题,可以在项目的public/index.php开头添加一行代码临时提高内存限制:
ini_set('memory_limit', '256M');
但这只是治标不治本的办法,长期来看还是要优化数据加载策略,否则数据量再增长还是会出问题。
内容的提问来源于stack exchange,提问作者Ravioli87

