MySQL递归CTE报错“Recursive query aborted after N iterations”本质是轮次计数器溢出,非数据量或锁导致;每轮迭代(无论是否产出行)计数+1,终止条件必须置于递归分支WHERE中,而非最终SELECT,且需防自循环、JOIN条件正确、避免CTE在JOIN左侧。

这不是锁或性能问题,而是轮次计数器实实在在溢出了——MySQL 每执行一轮递归(哪怕只产出 0 行),cte_max_recursion_depth 计数就 +1,到第 cte_max_recursion_depth + 1 轮启动前直接报错 ERROR 3636 (HY000): Recursive query aborted after N iterations。
为什么明明数据只有 5 层,却报了 1001 次?
递归深度 ≠ 树的实际层级,而是 MySQL 执行的迭代轮数。常见诱因:
- 终止条件写在错误位置:比如放在最终
SELECT的WHERE里,对递归过程无约束;必须写在递归分支的WHERE子句中(即UNION ALL后那个SELECT的WHERE) - 自循环未拦截:数据里存在
parent_id = id或 A→B→C→A 的环,每轮都匹配出旧行,永不返回空集 -
JOIN条件写反:例如写成ON d.id = dt.parent_id(应为d.parent_id = dt.id),导致每轮都用自身去查自己 - 用了
LEFT JOIN或把 CTE 放在JOIN左侧,语法虽可能通过,但逻辑上破坏驱动关系,实际行为不可控
如何快速定位是逻辑错误还是真需要调高深度?
别急着 SET SESSION cte_max_recursion_depth = 2000。先做两件事:
- 加
LIMIT 100到最终SELECT后(MySQL 8.0.19+ 支持),看前 100 行里有没有重复id、异常level跳变、或明显循环路径(如1→2→3→1) - 手动展开前 3 轮:用锚点结果手算第一轮 JOIN 输出,再用它算第二轮——常能一眼发现
ON条件或WHERE漏写 - 检查数据是否存在脏环:
SELECT * FROM departments WHERE id = parent_id OR (id, parent_id) IN (SELECT parent_id, id FROM departments)
为什么设高 cte_max_recursion_depth 反而更危险?
它不解决根本问题,只延迟报错,同时放大事务风险:
- 默认值 1000 是安全兜底,不是推荐值;生产环境建议按业务树深设为
50或100,倒逼你检查数据质量 - 若查询包在
START TRANSACTION里,每轮迭代都受隔离级别约束——某轮卡在等锁,整个事务挂起,锁持有时间直线上升 -
max_recursion_depth是会话级变量,应用每次连接都要重设;依赖全局配置容易漏,尤其连接池场景
真正关键的不是“能跑多少轮”,而是“第几轮开始产出无效行”。环检测、索引覆盖、SKIP LOCKED 配合使用,比调参数管用得多。


















