只有mysql 8.0+等支持递归cte的数据库才能原生查无限层级树;5.7及更早版本不支持with recursive,需用存储过程、应用层递归或固定深度join模拟,但后者最多4~5层且易性能爆炸。

MySQL 8.0+ 怎么用 WITH RECURSIVE 查树形结构
直接上结论:只有 MySQL 8.0+、PostgreSQL、SQL Server 2012+、Oracle 等支持递归 CTE 的数据库,才能用标准 SQL 原生查无限层级树;5.7 及更早版本不支持 WITH RECURSIVE,硬写子查询会卡死或只查两层。
典型场景是查部门组织架构、商品分类、评论回复链。假设表 categories 有 id、name、parent_id,要查 id=1 的所有子孙节点:
WITH RECURSIVE tree AS ( SELECT id, name, parent_id, 0 AS level FROM categories WHERE id = 1 UNION ALL SELECT c.id, c.name, c.parent_id, t.level + 1 FROM categories c INNER JOIN tree t ON c.parent_id = t.id ) SELECT * FROM tree ORDER BY level;
注意三点:UNION ALL 必须,不能用 UNION(性能差且可能截断);锚点查询(第一个 SELECT)必须能定位到根节点;递归部分的 JOIN 条件必须是“子.parent_id = 父.id”,反了就查不出数据。
MySQL 5.7 怎么绕过 WITH RECURSIVE 限制
没有递归 CTE,就只能靠应用层拼接、存储过程,或用固定深度的 LEFT JOIN 模拟——但后者最多撑 4~5 层,再深就爆炸。
常见错误是试图用关联子查询反复查 (SELECT ... WHERE parent_id = outer.id),结果要么语法报错,要么变成笛卡尔积拖垮性能。
- 安全做法:在应用代码里做 DFS/BFS,每次查一层,用
IN (id1,id2,...)批量拉取下级,控制总查询次数 - 折中做法:加一个
path字段(如"1/5/12/44"),用字符串匹配查子孙,但更新父节点时要批量改 path,容易出错 - 危险做法:写存储过程用临时表循环插入,调试困难,线上慎用
WHERE 条件为什么不能直接写在递归 CTE 外层
比如想查“所有 level SELECT * FROM tree WHERE level ,递归仍会跑完整棵树再过滤——可能查几万行才砍掉,极慢。
正确做法是把条件下推到递归内部:
WITH RECURSIVE tree AS ( SELECT id, name, parent_id, 0 AS level FROM categories WHERE id = 1 UNION ALL SELECT c.id, c.name, c.parent_id, t.level + 1 FROM categories c INNER JOIN tree t ON c.parent_id = t.id WHERE t.level <p>否则数据库不知道你要停在哪层,会一直递归到叶子节点甚至死循环(如果有环)。</p><h3>怎么防死循环(比如父子互相设为 parent_id)</h3><p>真实业务里数据脏,可能出现 A→B→A 这种环。MySQL 8.0 默认不限制递归次数,跑着跑着就超内存或超时。</p><p>必须加保护机制:</p>
- 用
MAXRECURSION(SQL Server)或cte_max_recursion_depth(MySQL 8.0.16+)设上限,比如SET SESSION cte_max_recursion_depth = 50 - 在递归 SELECT 中加路径记录和重复检测,例如追加
CONCAT(t.path, '/', c.id) AS path,再用WHERE c.id NOT IN (t.path)判断是否已出现过 - 上线前用
SELECT * FROM categories WHERE id = parent_id扫描自环,这类数据必须先修复
路径字段和递归深度控制不是可选项,是生产环境的保命配置。











