在业务系统中,部门、商品分类、评论回复等数据经常以父子关系保存在同一张表里。面对已知某个节点,需要把它下面所有层级的子节点全部拿出来这种需求,如果只靠应用层多次查询数据库,不仅代码繁琐,而且网络往返会带来明显延迟。下面整理几种在MySQL中查询子节点的实用做法。

一、使用WITH RECURSIVE实现递归查询
MySQL 8.0及以上版本支持公用表表达式(CTE),其中WITH RECURSIVE可以很方便地做层级展开。假设有表category,字段为id、name、parent_id。
-- 查询id为1的节点及其所有子节点
WITH RECURSIVE cte AS (
SELECT id, name, parent_id
FROM category
WHERE id = 1
UNION ALL
SELECT c.id, c.name, c.parent_id
FROM category c
INNER JOIN cte ON c.parent_id = cte.id
)
SELECT * FROM cte;
上面的语句先选出起始节点,再通过自连接不断找出下一层,直到没有新记录为止。对parent_id建立索引可以显著提升递归效率。
二、低版本MySQL的变量模拟法
如果数据库不支持递归CTE,可以利用用户变量拼接路径,再借助FIND_IN_SET匹配。
-- 假设已知根节点id为1
SELECT c.*
FROM (
SELECT id, parent_id,
@path := IF(parent_id = 1 OR FIND_IN_SET(parent_id, @path), CONCAT(@path, ',', id), NULL) AS path
FROM category, (SELECT @path := '1') AS v
ORDER BY id
) AS t
JOIN category c ON c.id = t.id
WHERE t.path IS NOT NULL;
这种写法依赖排序和变量赋值顺序,逻辑不够直观,仅建议作为兼容旧版本的临时方案。
三、左右值树模型(预排序遍历)
另一种思路是在表中冗余left_val和right_val,使整棵子树落在一段连续区间内。
| 字段 | 说明 |
|---|---|
| id | 节点主键 |
| left_val | 前序遍历左值 |
| right_val | 前序遍历右值 |
-- 查询左值在父节点范围内的所有子节点 SELECT child.* FROM category child, category parent WHERE parent.id = 1 AND child.left_val BETWEEN parent.left_val AND parent.right_val;
该方式查询极快,但插入和移动节点需要重算大量左右值,适合读多写少的场景。
四、方案对比与建议
- 递归CTE:语法清晰,维护简单,推荐MySQL 8.0+使用。
- 变量模拟:兼容性最好,但可读性与稳定性差。
- 左右值:查询性能最优,写入成本高。
实际选型时,应先确认数据库版本与读写比例,再决定采用哪种方式查询子节点。