如何限制PHP递归函数?解决树形递归内存溢出问题
解决递归加载树形结构导致浏览器内存耗尽的问题
我用递归函数生成流程图时,因为会遍历树形结构的所有子节点,导致浏览器内存耗尽,想找有效的递归限制方法。
原递归代码
if(mysqli_num_rows($rq)>0) { $kid = mysqli_fetch_assoc($rq); $sq = "SELECT groupe_id FROM groupe_tbl WHERE groupe_groupe_id=$groupe_id"; $rq = IPT_query($sq); if(mysqli_num_rows($rq)>0){ $kid['kids'] = array(); while($k=mysqli_fetch_assoc($rq)) { $kid['kids'][] = get_kids($k['groupe_id']); } } else $kid['kids'] = false; return $kid; }
已尝试但无效的方法
我试过限制单层级的子节点数量,但这只是减少了当前层的节点数,并没有从根本上解决递归遍历所有层级导致的内存问题:
while($i<20 and $k=mysqli_fetch_assoc($rq)) { $kid['kids'][] = get_kids($k['groupe_id']); $i++; }
可行的解决方案
1. 限制递归深度
给递归函数增加深度参数,设定最大允许遍历的层级,超过后停止加载子节点,从根源上减少节点总数。
function get_kids($groupe_id, $current_depth = 0, $max_depth = 3) { // 获取当前节点信息 $sq_node = "SELECT * FROM groupe_tbl WHERE groupe_id = $groupe_id"; $rq_node = IPT_query($sq_node); $kid = mysqli_fetch_assoc($rq_node); // 超过最大深度则停止加载子节点 if ($current_depth >= $max_depth) { $kid['kids'] = false; return $kid; } // 查询子节点 $sq_kids = "SELECT groupe_id FROM groupe_tbl WHERE groupe_groupe_id = $groupe_id"; $rq_kids = IPT_query($sq_kids); if(mysqli_num_rows($rq_kids) > 0){ $kid['kids'] = array(); while($k = mysqli_fetch_assoc($rq_kids)) { // 递归调用时深度+1 $kid['kids'][] = get_kids($k['groupe_id'], $current_depth + 1, $max_depth); } } else { $kid['kids'] = false; } return $kid; }
2. 用迭代替代递归
递归会占用大量调用栈内存,改用栈/队列实现迭代遍历,能更灵活控制节点加载,避免栈溢出和内存过载。
function get_kids_iterative($root_groupe_id, $max_depth = 3) { // 栈存储待处理节点:包含节点ID、当前深度、父节点ID $stack = [[ 'id' => $root_groupe_id, 'depth' => 0, 'parent_id' => null ]]; // 映射表存储已处理的节点,方便关联父节点 $node_map = []; $root_node = null; while (!empty($stack)) { $current = array_pop($stack); $groupe_id = $current['id']; $depth = $current['depth']; // 查询当前节点信息 $sq_node = "SELECT * FROM groupe_tbl WHERE groupe_id = $groupe_id"; $rq_node = IPT_query($sq_node); $node = mysqli_fetch_assoc($rq_node); $node['kids'] = []; // 未超过最大深度则加载子节点 if ($depth < $max_depth) { $sq_kids = "SELECT groupe_id FROM groupe_tbl WHERE groupe_groupe_id = $groupe_id"; $rq_kids = IPT_query($sq_kids); while ($child = mysqli_fetch_assoc($rq_kids)) { $child_id = $child['groupe_id']; // 将子节点加入栈等待处理 array_push($stack, [ 'id' => $child_id, 'depth' => $depth + 1, 'parent_id' => $groupe_id ]); $node['kids'][] = $child_id; } } // 存入映射表 $node_map[$groupe_id] = $node; // 关联到父节点的子列表 if ($current['parent_id'] === null) { $root_node = $node; } else { $parent_node = &$node_map[$current['parent_id']]; foreach ($parent_node['kids'] as &$kid_id) { if ($kid_id === $groupe_id) { $kid_id = $node; break; } } } } return $root_node; }
3. 前端懒加载
后端只返回第一层节点,前端点击节点时再请求该节点的直接子节点,避免一次性加载全量数据。后端接口示例:
// 获取指定节点的直接子节点(不递归) function get_direct_kids($groupe_id) { $sq = "SELECT * FROM groupe_tbl WHERE groupe_groupe_id = $groupe_id"; $rq = IPT_query($sq); $kids = []; while ($row = mysqli_fetch_assoc($rq)) { $row['has_kids'] = false; // 判断是否有子节点,给前端显示展开按钮 $sq_check = "SELECT 1 FROM groupe_tbl WHERE groupe_groupe_id = {$row['groupe_id']} LIMIT 1"; $rq_check = IPT_query($sq_check); if (mysqli_num_rows($rq_check) > 0) { $row['has_kids'] = true; } $kids[] = $row; } return json_encode($kids); }
4. 限制总加载节点数
在递归过程中维护一个计数器,当总节点数达到设定值时,停止加载新的子节点:
function get_kids_with_limit($groupe_id, &$total_count = 0, $max_count = 50) { if ($total_count >= $max_count) { return null; } $sq_node = "SELECT * FROM groupe_tbl WHERE groupe_id = $groupe_id"; $rq_node = IPT_query($sq_node); $kid = mysqli_fetch_assoc($rq_node); $total_count++; $sq_kids = "SELECT groupe_id FROM groupe_tbl WHERE groupe_groupe_id = $groupe_id"; $rq_kids = IPT_query($sq_kids); $kid['kids'] = []; while ($k = mysqli_fetch_assoc($rq_kids)) { if ($total_count >= $max_count) { break; } $child = get_kids_with_limit($k['groupe_id'], $total_count, $max_count); if ($child) { $kid['kids'][] = $child; } } if (empty($kid['kids'])) { $kid['kids'] = false; } return $kid; }
内容的提问来源于stack exchange,提问作者Hosni ISMAIL
相关产品推荐
相关产品推荐

