MySQL 8.0中递归CTE不能直接用于INSERT … SELECT,必须分两步:先用CREATE TEMPORARY TABLE + WITH RECURSIVE生成中间结果,再INSERT SELECT;需设置cte_max_recursion_depth和group_concat_max_len,并用GROUP_CONCAT按level拼接路径。

MySQL 8.0递归CTE不能直接用于INSERT语句
MySQL 8.0支持递归CTE,但语法限制明确:WITH RECURSIVE子句不能作为INSERT ... SELECT中SELECT部分的顶层结构——会报错ERROR 1235 (42000): This version of MySQL doesn't yet support 'INSERT ... SELECT ... WITH ...'。这不是配置或权限问题,是解析器层面的硬性限制。
绕过方法只有一个:先用递归CTE生成结果集并存入临时表,再从临时表插入目标表。
正确写法:分两步走,中间必须用TEMPORARY TABLE
临时表是唯一安全、可预测的中转方式。注意不能用派生表(subquery in FROM)替代,因为递归CTE不能嵌套在子查询中被INSERT引用;也不能用普通表,否则并发写入可能污染数据。
CREATE TEMPORARY TABLE tmp_nodes AS WITH RECURSIVE tree AS (SELECT id, name, parent_id, 1 AS level FROM categories WHERE parent_id IS NULL UNION ALL SELECT c.id, c.name, c.parent_id, t.level + 1 FROM categories c INNER JOIN tree t ON c.parent_id = t.id) SELECT * FROM tree;INSERT INTO catalog_items (node_id, path, depth) SELECT id, CONCAT('/', GROUP_CONCAT(name ORDER BY level SEPARATOR '/')), level FROM tmp_nodes t JOIN categories c ON t.id = c.id GROUP BY id, level;- 临时表在会话结束时自动销毁,无需手动
DROP
递归CTE常见坑:MAXRECURSION和初始种子选错
MySQL默认递归深度上限是1000,超限会中断并报错ERROR 3636 (HY000): Recursive query aborted after 1000 iterations。必须显式设置cte_max_recursion_depth系统变量(会话级),且需在CTE之前设好。
- 执行前加:
SET SESSION cte_max_recursion_depth = 3000;(根据实际层级预估,别盲目设极大值) - 初始查询(anchor member)必须能独立返回非空结果,否则整个递归不触发——例如
WHERE parent_id IS NULL若无数据,后续UNION ALL完全不会执行 - 递归成员中
JOIN条件必须关联到上一层的输出列(如t.id),写成c.id = t.parent_id就逻辑反了,会导致无限循环或空结果
INSERT时路径拼接要用GROUP_CONCAT而非字符串累加
递归CTE展开后,每个节点只有一行,但完整路径需要从根到当前节点所有name串联。不能靠递归层内变量累加(MySQL不支持递归中的用户变量赋值),必须靠聚合。
关键点:
-
GROUP_CONCAT(name ORDER BY level SEPARATOR '/')依赖level字段排序,确保根在前、叶子在后 - 如果原始表没存
level,必须在CTE里生成(如示例中的1 AS level和t.level + 1) -
GROUP BY id, level防止因同名节点或重复数据导致路径错乱 - 若路径含特殊字符(如单引号、反斜杠),需提前用
REPLACE()处理,否则影响后续SQL解析
递归深度大、节点多时,GROUP_CONCAT默认长度限制(1024)会截断路径,务必同步调大:SET SESSION group_concat_max_len = 1000000;


















