应采用一次查全表+php递归构树,避免递归中重复查库和静态数组累积;用引用挂载子节点,加深度限制防死循环;删分类时需倒序或事务处理;务必启用缓存。

直接用递归查数据库在 CodeIgniter 里能跑通,但数据一过百条就明显卡顿,nolimit() 这类静态数组累积的写法还容易内存溢出——这不是写法错,是没避开 CI 的查询机制缺陷。
递归函数里反复全表查,性能崩得比预期快
原始代码里 nolimit() 每次递归都传入完整结果集 $crr,看似省了一次查询,实则把所有分类数据全加载进内存,再靠 PHP 循环过滤。100 条数据时内存占用翻倍,500 条以上可能触发 Fatal error: Allowed memory size exhausted。
- 真正该做的是:只在第一次查全表,后续全部靠数组遍历,不重复查库
- 去掉
static $arr = array(),改用传引用或返回值拼接,避免静态变量跨请求残留 - 加个层级深度限制,比如
if ($level > 10) return [];,防死循环
用一次查询 + 引用构建树,比递归调用更稳
CI 自带的 Query Builder 不支持 WITH RECURSIVE,但可以用「一次查全 + PHP 构树」替代。核心是把父子关系存在键名里,靠引用直接挂载子节点。
- 查库语句保持简单:
$this->db->get('category')->result_array(); - 构造空数组
$tree = [];和引用映射$refs = []; - 遍历结果时:如果
$row['parent_id'] == 0,推入$tree;否则用$refs[$row['parent_id']][] = &$row挂载 - 最后递归补全子节点(不查库),函数签名类似
buildTree($nodes, $parentId = 0)
前端模板里缩进显示,别靠 str_repeat() 硬撑
str_repeat(' ', $val['level']) 在 UTF-8 下会把中文挤歪,且无法响应式缩进。实际渲染应交给 CSS 控制层级视觉。
- 后端只传
level字段,前端用margin-left: calc(1rem * {$val['level']})或 class 名如level-2 - 模板中避免嵌套三层
foreach,改用递归视图片段(CI 支持$this->load->view('category_item', ['item' => $item])) - 如果必须用固定层数(如只到三级),
sel_child()里手动控制循环次数,别无脑套三层foreach
删分类时,子分类不是自动消失的
CI 的 delete() 默认不级联,sel_son($id) 返回子集后,你得自己循环删——但顺序错了会外键报错。
- 先递归查出所有后代 ID(用上面的
buildTree辅助生成 ID 列表),再按倒序删(从叶子往根) - 或者改用事务:
$this->db->trans_start(); ... $this->db->trans_complete(); - 千万别在模型里直接写
$this->db->where("parent_id=$id")->delete(),没防 SQL 注入,$id必须用escape()或绑定参数
最常被跳过的其实是缓存。分类树基本不怎么变,查一次就该 cache()->save('category_tree', $data, 3600) 存上一小时,而不是每次请求都重跑那几层 foreach。











