mysql 5.7 存储过程不支持递归调用,必须用 while 循环+临时表模拟;需建带 id/name/level 的临时表并加唯一约束,每轮插入前去重,用 row_count() 控制终止,向上查祖辈时须设最大层级防环,推荐升级至 8.0+ 使用 with recursive。

MySQL 5.7 存储过程不能递归调用,必须用循环展开
你写 CALL get_descendants(1) 然后在过程体里再 CALL get_descendants(t.id) —— 这会直接报错 ERROR 1424 (HY000): Recursive stored functions and triggers are not allowed。这不是语法问题,是 MySQL 引擎层硬性禁止。所以所谓“存储过程实现递归”,本质是用 WHILE + 临时表手动模拟递归展开过程。
必须用 CREATE TEMPORARY TABLE,不能只靠用户变量
用户变量(如 @ids)只能存单值或逗号拼接的字符串,无法承载多行、多列、带层级深度的数据,也无法做集合去重或 JOIN。一旦层级变深或存在多路径(比如两个父节点都指向同一个子节点),FIND_IN_SET 就会漏数据或死循环。
- 临时表要包含完整目标字段,例如
id、name、level,且建议加PRIMARY KEY(id)或UNIQUE KEY(id) - 每次插入前必须加
WHERE c.id NOT IN (SELECT id FROM temp_result),否则重复插入导致无限循环 -
DROP TEMPORARY TABLE IF EXISTS temp_result必须放在开头,避免上一次执行残留影响当前结果
INSERT INTO ... SELECT 的 WHERE 条件容易写错
核心逻辑是:每轮把「当前已有的所有节点的直接子节点」追加进去。常见错误是把条件写成 WHERE c.parent_id = t.id 却忘了限制「这些 t.id 必须是上一轮刚插入的、还没被处理过的」——这会导致每轮都重新扫全表,性能爆炸,还可能重复插入。
- 正确写法依赖临时表自身:用
WHERE c.parent_id IN (SELECT id FROM temp_result)获取下一层候选 - 但必须叠加去重:加上
AND c.id NOT IN (SELECT id FROM temp_result) - 用
SELECT ROW_COUNT() INTO row_count判断本轮是否插入了新行,为0就LEAVE
向上查祖辈(查父级路径)比向下查后代更难防环
向下查后代时,树结构天然无环(子不能是自己的祖先);但向上查时,如果数据脏(比如某条记录的 parent_id 指向自己,或形成 A→B→A 循环),纯靠 NOT IN 无法拦截。这时需要额外控制最大迭代次数:
- 声明
DECLARE max_level INT DEFAULT 10 - 循环内加
IF t.level >= max_level THEN LEAVE loop_label; END IF; - 尤其在组织架构同步出错、BOM 表异常导入等场景,这个兜底非常关键
临时表方案看着啰嗦,但它能精确控制中间状态、支持层级标记、可中断可调试——比拼字符串或嵌套子查询更可靠。真要频繁查树,别硬扛 5.7,升级到 8.0+ 用 WITH RECURSIVE 才是正解。











