终止条件必须写在递归成员的WHERE子句里,递归停止的唯一依据是某轮递归查询返回空结果集;若漏写、错写或置于外层SELECT,优化器无法提前截断,将跑满最大递归深度后报错。

终止条件必须写在递归成员的 WHERE 子句里
递归停止的唯一依据是“某轮递归查询返回空结果集”,不是靠计数器或时间。如果你把 WHERE 写在最外层 SELECT,或者漏掉、写错条件,优化器根本不会提前截断迭代,而是硬跑满 cte_max_recursion_depth 后报错 ERROR 3636 (HY000): Recursive query aborted after 1001 iterations。
常见错误包括:
WHERE n 写成 <code>WHERE n → 多算一轮,可能触发超限- 用
st.level <= 5代替st.level < 5→ 层级判断边界偏移 - 锚点查
manager_id = 1001,递归时却用e.id = st.manager_id(方向反了),导致永远找不到下级,无限循环
数据有环时单靠层级限制不够
树形结构若存在 A→B→C→A 这类自循环,仅靠 level < 5 无法阻止死循环:它可能在第 4 层回到 A,第 5 层又走到 B,第 6 层再进 C……直到撞上限才报错。
MySQL 8.0.28+ 支持路径数组防环,推荐写法:
WITH RECURSIVE dept_tree AS (
SELECT id, name, parent_id, 1 AS level, CAST(id AS CHAR(1000)) AS path
FROM departments WHERE parent_id IS NULL
UNION ALL
SELECT d.id, d.name, d.parent_id, dt.level + 1,
CONCAT(dt.path, ',', d.id)
FROM departments d
INNER JOIN dept_tree dt ON d.parent_id = dt.id
WHERE d.id NOT IN (SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(dt.path, ',', nums.n), ',', -1)
FROM numbers nums
WHERE nums.n <= LENGTH(dt.path) - LENGTH(REPLACE(dt.path, ',', '')) + 1)
)更简洁的做法(8.0.28+):
- 用
MEMBER OF判断:WHERE d.id MEMBER OF (JSON_EXTRACT(dt.path, '$'))不成立才继续 - 但注意
path必须存为 JSON 数组,否则需先JSON_CONTAINS配合CAST
递归成员必须放在 JOIN 右侧
MySQL 强制要求递归 CTE 在每轮中只能作为被驱动表(即 JOIN 的右表)。如果写成 FROM dept_tree dt INNER JOIN departments d,会直接报错 ERROR 3641 (HY000): Recursive reference to CTE 'dept_tree' is not allowed in this context。
原因在于执行模型:每轮必须以上一轮 CTE 输出为驱动源,去探查基础表。颠倒顺序会导致优化器无法构建迭代链。
- ✅ 正确:
FROM departments d INNER JOIN dept_tree dt ON d.parent_id = dt.id - ❌ 错误:
FROM dept_tree dt INNER JOIN departments d ON d.parent_id = dt.id - ❌ 错误:
FROM departments d LEFT JOIN dept_tree dt ON ...(LEFT JOIN右侧也不允许)
调试阶段务必加 LIMIT,别只调大 cte_max_recursion_depth
MySQL 8.0.19+ 支持在递归 CTE 最终 SELECT 后加 LIMIT,它会在结果行数超限时主动中断,不等迭代跑满。这比盲目调高 cte_max_recursion_depth 更安全、更可控。
比如:
WITH RECURSIVE numbers AS ( SELECT 1 AS n UNION ALL SELECT n + 1 FROM numbers WHERE n < 1000 ) SELECT * FROM numbers LIMIT 20;
即使 WHERE 条件写错,也只会返回前 20 行,方便你肉眼确认路径是否异常。生产环境上线前,建议先用 LIMIT 100 跑通逻辑,再逐步放开。
真正难的不是写出语法正确的递归 CTE,而是确保它在线上面对百万节点、脏数据、隐式环时,仍能收敛——这取决于你对数据拓扑的理解,而不是对 SQL 关键字的熟悉程度。


















