MySQL 8.0+ 直接用 WITH RECURSIVE 是最干净的解法;低于 8.0 应用路径枚举或闭包表替代,需注意 level 初始化、UNION ALL 层级叠加、max_recursion_depth 调优及索引优化。

MySQL 8.0+ 直接用 WITH RECURSIVE 是最干净的解法;低于 8.0,别硬套递归,老老实实用路径枚举或闭包表。
WITH RECURSIVE 查询子树时 level 计算不准?
常见错误是把锚定查询和递归部分的 level 初始化搞反,或者漏掉 UNION ALL 中的层级叠加逻辑。
- 锚定部分(根节点)必须显式设
level = 1,不能依赖默认值 - 递归部分必须写
ct.level + 1,不能写成level + 1(否则报列不存在) - 如果起点不是根节点(比如查某个中间部门的所有下属),锚定条件要基于
id而非parent_id IS NULL -
max_recursion_depth默认 1000,超深树要提前调大:SET SESSION max_recursion_depth = 2000;
MySQL 5.7 怎么查某节点所有后代?用 path 字段最稳
加一个 path 字段存祖先 ID 序列(如 '0,1,5,198'),再建前缀索引,比反复自连接快得多。
- 插入时:取父节点
path值,拼上当前id,如CONCAT(parent.path, ',', NEW.id) - 查所有子树:
WHERE path LIKE '0,1,5,%',注意末尾逗号可省,但开头必须精确匹配 - 必须建索引:
CREATE INDEX idx_path ON nodes(path(255));,不然LIKE会全表扫 - 更新/删除时需级联更新子节点
path,建议用触发器,但要注意触发器里不能修改同表——得用存储过程或应用层兜底
闭包表查祖先 vs 查后代,SQL 写法完全不同
闭包表(tree_path(ancestor, descendant))查方向不同,WHERE 条件主谓颠倒,容易写反。
- 查某节点(ID=5)的所有祖先:
SELECT ancestor FROM tree_path WHERE descendant = 5; - 查某节点(ID=5)的所有后代:
SELECT descendant FROM tree_path WHERE ancestor = 5; - 查某节点直属子节点(仅下一层):
SELECT descendant FROM tree_path WHERE ancestor = 5 AND distance = 1;(需额外存distance字段) - 初始化闭包表不能只插父子对,必须包含自环(
ancestor = descendant)和所有间接关系,可用存储过程生成,但大数据量时耗时明显
真正麻烦的不是查,是写——路径枚举怕更新扩散,闭包表怕关系爆炸,嵌套集怕重平衡。选哪种,先看写多还是读多,再看 MySQL 版本卡不卡。别等上线后才发现 WITH RECURSIVE 用不了,也别在千万级节点上硬跑闭包表插入。


















