
本文介绍两种高效获取指定分类及其全部后代分类 id 的方法:基于 mysql 8.0+ 的递归 cte 查询,以及 php 侧递归遍历;支持自包含(返回 parent_id 自身),适用于无限级分类场景。
本文介绍两种高效获取指定分类及其全部后代分类 id 的方法:基于 mysql 8.0+ 的递归 cte 查询,以及 php 侧递归遍历;支持自包含(返回 parent_id 自身),适用于无限级分类场景。
在构建电商、CMS 或内容管理系统时,常需根据一个父分类 ID 获取其完整子树(含自身)的所有分类 ID,例如:输入 id = 1,期望返回 [1, 3, 4, 5, 6](即该节点及其所有直接/间接子节点)。这本质上是树形结构的深度优先遍历问题。下面提供两种生产环境推荐方案:
✅ 方案一:MySQL 8.0+ 递归 CTE(推荐 · 单次查询、高性能)
若数据库为 MySQL 8.0 或更高版本,使用 WITH RECURSIVE 是最简洁、高效的方式。关键在于:将起始节点(parent_id)作为递归起点,并在结果中显式包含它。
WITH RECURSIVE category_tree AS ( -- 基础查询:从指定 parent_id 开始(注意:此处不是找根节点,而是找自身 + 所有后代) SELECT id, name, parent_id, status, ordering FROM categories WHERE id = ? -- 绑定参数:目标 parent_id(如 1) UNION ALL -- 递归查询:查找当前结果集中所有节点的直接子节点 SELECT c.id, c.name, c.parent_id, c.status, c.ordering FROM categories c INNER JOIN category_tree ct ON c.parent_id = ct.id ) SELECT id FROM category_tree ORDER BY id;
✅ 优势:单次数据库查询、无 PHP 循环开销、天然支持排序与分页、可轻松扩展为带层级 depth 字段。
⚠️ 注意:确保 categories 表上有 (parent_id) 索引以提升 JOIN 性能。
在 PHP 中调用示例(PDO):
function getAllChildIdsByParentId(PDO $pdo, int $parentId): array {
$sql = "WITH RECURSIVE category_tree AS (
SELECT id FROM categories WHERE id = ?
UNION ALL
SELECT c.id FROM categories c
INNER JOIN category_tree ct ON c.parent_id = ct.id
) SELECT id FROM category_tree ORDER BY id";
$stmt = $pdo->prepare($sql);
$stmt->execute([$parentId]);
return array_column($stmt->fetchAll(PDO::FETCH_NUM), 0);
}
// 使用示例
$ids = getAllChildIdsByParentId($pdo, 1); // 返回 [1, 3, 4, 5, 6]
✅ 方案二:PHP 递归函数(兼容旧版 MySQL · 需谨慎使用)
当数据库不支持 CTE(如 MySQL 避免 N+1 查询问题,应预先加载全量数据并建立索引映射。
function getAllChildIdsRecursive(array $allCategories, int $parentId): array {
$result = [$parentId]; // 包含自身
$children = array_filter($allCategories, fn($cat) => $cat['parent_id'] == $parentId);
foreach ($children as $child) {
$result = array_merge($result, getAllChildIdsRecursive($allCategories, $child['id']));
}
return array_values(array_unique($result)); // 去重并重置键
}
// 使用前:一次性查出全部分类(建议加索引优化)
$all = $pdo->query("SELECT id, parent_id FROM categories")->fetchAll(PDO::FETCH_ASSOC);
$ids = getAllChildIdsRecursive($all, 4); // 返回 [4, 5, 6]
⚠️ 重要提醒:
- ❌ 切勿在循环内执行数据库查询(如原答案中的 $this->getAllChildrenForParent($row['id'])),会导致严重性能问题;
- ✅ 推荐预加载全表 + 内存递归,或改用迭代(栈模拟)避免 PHP 递归深度限制;
- ? 生产环境务必对 $parentId 做整型校验与边界检查(防止 SQL 注入或非法 ID)。
? 总结建议
| 场景 | 推荐方案 |
|---|---|
| MySQL ≥ 8.0,追求性能与简洁 | ✅ 使用 WITH RECURSIVE CTE(首选) |
| 低版本 MySQL 或需动态过滤子树属性 | ✅ 预加载全量数据 + PHP 内存递归 |
| 树极深(>100 层)、并发高 | ⚠️ 考虑路径枚举(path)或闭包表(closure table)等替代模型 |
无论采用哪种方式,最终目标都是安全、高效、可维护地获取一棵子树的完整 ID 集合——这是构建分类筛选、权限继承、面包屑导航等功能的基础能力。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











