MySQL 8.0递归CTE需显式启用且检查cte_max_recursion_depth是否大于0,递归成员禁止GROUP BY/ORDER BY/LIMIT,层级聚合和排序必须移至外层SELECT中实现。

MySQL 8.0 的递归 CTE 本身不自动优化执行,性能好坏几乎全取决于你怎么写、怎么用索引、怎么设终止条件——写错一句,查询就卡死或返回错层。
递归 CTE 必须显式启用且检查 cte_max_recursion_depth
即使你用的是 MySQL 8.0.1+,递归功能也可能被禁用:某些云厂商默认把 cte_max_recursion_depth 设为 0。不查就直接写 WITH RECURSIVE,会报 ERROR 3636 (HY000): Recursive common table expression is not supported 或更误导的 ERROR 1223。
- 先执行
SELECT @@cte_max_recursion_depth;确认值是否 > 0 - 若为 0,需有 SUPER 权限才能运行
SET GLOBAL cte_max_recursion_depth = 1000; - 临时调高(如查 20 层树)可用会话级:
SET SESSION cte_max_recursion_depth = 30;,避免影响其他连接
递归成员里不能写 GROUP BY / ORDER BY / LIMIT
MySQL 把递归部分当成“单轮迭代生成器”,不是完整 SELECT。只要你在递归分支里加了 GROUP BY、ORDER BY 或 LIMIT,就会立刻报错,比如 Recursive member cannot contain GROUP BY 或 ERROR 3641。
- 层级内聚合(如统计每层节点数)必须挪到最终
SELECT中,例如:SELECT level, COUNT(*) FROM dept_tree GROUP BY level - 排序也一样:递归部分不能
ORDER BY,但外层SELECT * FROM dept_tree ORDER BY level, id完全合法 - 想提前截断?靠
WHERE level 这类条件过滤,而不是 <code>LIMIT
JOIN 方向和索引决定实际性能,不是语法漂亮就行
递归执行是“上轮输出 → 驱动 JOIN → 生成本轮新行”,所以 ON 条件字段必须有索引,且方向要对。常见低效写法是让子表去 JOIN 递归 CTE,而 CTE 应该作为被驱动表(即放在 JOIN 右侧)。
- 正确:
FROM departments d INNER JOIN dept_tree dt ON d.parent_id = dt.id——dt是小集合(上轮结果),d是大表,走departments.parent_id索引 - 错误:
FROM dept_tree dt INNER JOIN departments d ON dt.id = d.parent_id——dt每轮可能膨胀,没索引可走,容易全表扫描 - 务必确保
parent_id字段有索引;如果常查某节点的所有祖先(向上递归),则id字段也要有索引
防环和调试必须靠 level + 显式 WHERE,不能依赖数据干净
MySQL 不检测数据环路。哪怕只有一条记录 parent_id = id,整个递归就会跑满深度上限才中止,期间 CPU 和内存持续飙升,最后报 Recursive query aborted after 1001 iterations。
- 每层加
level字段(如0 AS level→dt.level + 1),查出来第一眼就能看出是否重复或突增 - 强制终止条件写在递归分支的
WHERE里,例如:WHERE d.parent_id = dt.id AND dt.level - 路径防环(如拼接
CONCAT(path, ',', id))虽可行,但字符串操作开销大,仅在业务强要求时用
真正卡住你的从来不是语法记不住,而是 level 没加、索引没建、cte_max_recursion_depth 没查、ON 条件写反了——这些点漏一个,查询就从“秒出”变成“杀进程”。


















