mysql 8.0+递归查询首选with recursive而非存储过程,因后者绕过优化器导致索引失效、i/o激增、并发冲突及调试困难;with recursive须严守四铁律:显式声明recursive、仅用union all、cte名单次出现且不在join右侧、终止条件须由列值驱动。

MySQL 8.0 之后,递归查询首选 WITH RECURSIVE,存储过程实现递归是兼容旧版本的权宜之计,不是推荐路径。
为什么不该用存储过程写递归查询
存储过程模拟递归本质是“手动循环 + 临时表”,它绕过了优化器对层级数据的天然理解,带来三类硬伤:
- 无法利用索引下推(ICP)和物化 CTE 的执行计划优化,
EXPLAIN显示全表扫描频次高 - 每轮
INSERT INTO temp_table都触发一次磁盘 I/O,层级深时性能断崖式下降 - 并发执行时临时表名冲突风险高(即使加
CONNECTION_ID()拼接也难保万无一失) - 调试困难:错误堆栈不指向具体递归步,只报
REPEAT块末尾
WITH RECURSIVE 必须遵守的四条铁律
写错任意一条,轻则结果缺失,重则报错 Recursive query aborted after 1001 iterations:
- 必须显式写
WITH RECURSIVE,漏掉RECURSIVE关键字直接报语法错 - 锚成员(anchor)和递归成员之间只能用
UNION ALL,用UNION会去重导致子节点丢失 - 递归成员的
FROM子句里,CTE 名称只能出现一次,且不能在子查询或LEFT JOIN右侧 - 终止条件必须由
JOIN或WHERE中的列值驱动,不能依赖函数如NOW()或RAND()
层级深度超限怎么办:不只是调大 cte_max_recursion_depth
默认 1000 层看似够用,但实际中常因逻辑错误被耗尽——比如父子关系成环、parent_id 值为空字符串而非 NULL,都会让递归停不下来:
- 先查环:
SELECT id, parent_id FROM your_table WHERE parent_id = id,这种自引用必须提前清理 - 检查空值语义:
parent_id是INT类型却存了' '字符串?会导致JOIN永远匹配失败,递归无限进行 - 真要调参,用会话级设置:
SET SESSION cte_max_recursion_depth = 3000,避免影响全局 - 更稳妥的做法是在递归成员里加防护层:
WHERE cte.level ,用业务可接受的最大深度兜底
真正棘手的从来不是语法怎么写,而是数据里藏着的隐性环、类型混用、空值歧义——这些不会在 CREATE PROCEDURE 时报警,却会在某次 SELECT 时突然卡死整个连接。











