Laravel 9递归加载自关联模型时内存耗尽问题求助
解决方案:Laravel9递归加载自关联父级内存耗尽问题
问题根源
你在模型parent()关联方法中直接嵌套->with('parent'),会触发无限递归加载:每次加载父级模型时,又会自动加载该父级的父级,直到PHP内存耗尽。Laravel5的关联加载逻辑可能在处理这种递归时存在隐性终止条件(比如父级为null时自动停止),但Laravel9的预加载逻辑优化后,这种嵌套写法会导致无限制的递归查询。
具体解决方法
方法1:移除关联内的预加载,改用可控的递归预加载
首先修改模型的关联定义,去掉关联方法内的with('parent'):
// 模型文件(比如Category.php) public function parent() { return $this->belongsTo(self::class, 'parent_id'); }
然后在查询时,通过闭包实现带深度限制的递归预加载,避免无限递归:
// 自定义递归加载函数,控制加载层级 function loadParentRecursive($query, $maxDepth = 3) { if ($maxDepth <= 0) return; $query->with(['parent' => function ($q) use ($maxDepth) { loadParentRecursive($q, $maxDepth - 1); }]); } // 查询时调用 $items = YourModel::query() ->tap(function ($query) { loadParentRecursive($query, 3); // 限制最多加载3级父级 }) ->get();
方法2:检查并修复数据循环引用
如果数据中存在循环引用(比如某条记录的parent_id指向自身,或形成A→B→A的闭环),也会导致无限递归。可以通过SQL查询排查:
-- 查找自身引用的记录 SELECT * FROM your_table WHERE id = parent_id; -- 查找闭环引用(需要MySQL8+的CTE) WITH RECURSIVE cycle_check AS ( SELECT id, parent_id, 1 AS depth, CONCAT(id) AS path FROM your_table WHERE parent_id IS NOT NULL UNION ALL SELECT t.id, t.parent_id, cc.depth + 1, CONCAT(cc.path, '→', t.id) FROM your_table t JOIN cycle_check cc ON t.parent_id = cc.id WHERE cc.depth < 10 AND NOT FIND_IN_SET(t.id, cc.path) ) SELECT * FROM cycle_check WHERE FIND_IN_SET(parent_id, path);
找到循环引用后,修正parent_id字段值即可。
方法3:使用延迟加载替代预加载
如果不需要一次性加载所有父级,可以改用延迟加载,在需要时动态加载,同时手动控制终止条件:
// 在模型中添加方法 public function loadAllParents() { $current = $this; while ($current->parent_id && !$current->relationLoaded('parent')) { $current = $current->loadMissing('parent')->parent; } return $this; } // 使用时 $model = YourModel::find($id); $model->loadAllParents();
方法4:用MySQL8递归CTE直接查询所有父级
利用MySQL8的CTE递归查询,一次性获取所有层级的父级数据,再手动组装模型关系,避免ORM递归加载的开销:
$modelId = 1; // 递归查询所有父级(排除自身) $parentRecords = DB::select(" WITH RECURSIVE parent_tree AS ( SELECT id, parent_id, name FROM your_table WHERE id = ? UNION ALL SELECT t.id, t.parent_id, t.name FROM your_table t JOIN parent_tree pt ON t.id = pt.parent_id ) SELECT * FROM parent_tree WHERE id != ? ORDER BY id DESC ", [$modelId, $modelId]); // 组装模型关系 $model = YourModel::find($modelId); $currentModel = $model; foreach ($parentRecords as $record) { $parentModel = new YourModel($record); $currentModel->setRelation('parent', $parentModel); $currentModel = $parentModel; }
内容的提问来源于stack exchange,提问作者CTR
相关产品推荐
相关产品推荐

