MySQL 8.0+ 必须用 WITH RECURSIVE 实现递归查询,锚点在 UNION ALL 前、不能引用递归表名,递归部分需显式关联上层结果并设终止条件,否则报错或无限循环。

MySQL 8.0+ 怎么写递归查询:必须用 WITH RECURSIVE
MySQL 在 8.0 之前根本不支持递归查询,强行用自连接模拟只会漏数据或死循环;8.0+ 必须用 WITH RECURSIVE,否则语法直接报错 ERROR 1235 (42000): This version of MySQL doesn't yet support 'recursive'.
常见错误是漏掉 RECURSIVE 关键字,或者把锚点(anchor)和递归(recursive)部分写反顺序——锚点必须在 UNION ALL 前面,且只能返回初始层级的数据。
- 锚点查询不能依赖递归表名,否则报错
Unknown table 't' in field list - 递归部分必须通过
JOIN或WHERE显式关联上一层结果,比如ON t.parent_id = r.id - 必须有终止条件(比如
level < 5),否则可能无限递归,触发max_recursive_depth限制(默认 1000)
PostgreSQL 的递归 CTE 写法更宽松,但要注意 cycle 检测
PostgreSQL 支持 WITH RECURSIVE 且语法更灵活,比如允许在递归部分用 SELECT DISTINCT 或子查询,但默认不检测环路——如果数据本身存在父子闭环(比如 A→B→A),查询会卡死或超时。
真实业务中树形结构偶尔有脏数据,必须加 CYCLE 子句,否则上线后某天突然查不动。
- 加
CYCLE id SET is_cycle USING path后,is_cycle列为true表示遇到环 -
path是自动生成的数组,记录遍历过的id序列,可用于调试 - 不加
CYCLE时,只要数据里有一条环,整个查询就 hang 住,不会报错
SQL Server 的 CTE 递归要小心 MAXRECURSION 和 NULL 父节点
SQL Server 默认只递归 100 层,超过就报错 Msg 530, Level 16, State 1: The statement terminated. The maximum recursion 100 has been exhausted before statement completion.,必须显式加 OPTION (MAXRECURSION n),设成 0 表示无限制(慎用)。
另一个高频坑是根节点的 parent_id 为 NULL,但递归部分写了 WHERE parent_id = t.id ——这会导致根节点无法向下展开,因为 NULL = anything 永远为 false。
- 锚点应选
WHERE parent_id IS NULL的记录 - 递归部分用
INNER JOIN或WHERE t.parent_id = r.id,别用=判断NULL -
OPTION (MAXRECURSION 0)要配合业务深度预估,避免意外跑满 CPU
CTE 递归性能差?先看执行计划里的 Nested Loop
所有数据库的递归 CTE 底层都是迭代执行:每层结果集生成后,再拿去驱动下一层。如果父表没索引,或者连接字段类型不一致(比如 id 是 BIGINT,parent_id 是 VARCHAR),执行计划里会出现全表扫描 + Nested Loop,10 层深可能扫出百万行。
不是 CTE 本身慢,是基础连接慢。优化永远从索引和类型对齐开始。
- 确保
parent_id字段有索引,且和被引用的id类型完全一致 - 避免在递归部分 SELECT 中用子查询或函数(如
(SELECT COUNT(*) FROM ...)),它会在每一层重复执行 - PostgreSQL 可用
MATERIALIZED提示强制物化中间结果,但 MySQL/SQL Server 不支持
递归深度超过 20 层、单层结果超千行时,就得怀疑是不是该换方案了——比如预计算路径(lft/rgt)、或应用层分批拉取。CTE 递归适合清晰的树,不适合稠密网状关系。

















