MySQL 8.0中递归CTE必须显式使用WITH RECURSIVE声明,漏写即报语法错误;锚点与递归部分列数、类型须严格一致,路径拼接需统一类型,终止依赖结果为空而非WHERE硬限。

递归CTE必须用WITH RECURSIVE显式声明
MySQL 8.0不接受省略RECURSIVE关键字的写法,WITH cte AS (...)会直接报错ERROR 1235 (42000): This version of MySQL doesn't yet support 'recursive CTEs'。必须写成WITH RECURSIVE cte_name AS (...),否则连语法校验都过不去。
常见错误是照搬其他数据库(如PostgreSQL)习惯,漏掉RECURSIVE;或者误以为只要写了UNION ALL就自动触发递归——其实没有RECURSIVE,MySQL就当普通CTE处理,递归成员里的自引用会报Unknown table 'cte_name' in field list。
锚点和递归部分的列数、类型必须严格一致
两个SELECT子句的字段数量、顺序、数据类型要能隐式兼容,否则执行时报ERROR 1222 (21000): The used SELECT statements have a different number of columns或类型转换失败。比如锚点里用CAST('root' AS CHAR(100)) AS path,递归部分就得用CONCAT(dp.path, '/', d.name)保持同为字符串,不能突然换成d.id。
- 层级深度字段推荐统一用
INT,避免锚点写1而递归写depth + 1.0导致类型升格 - 路径拼接必须用
CAST或CONVERT初始化类型,否则空值参与CONCAT会让整列变成NULL - 如果表字段含
TEXT或JSON,递归部分别直接选,先CAST成VARCHAR再拼接
递归终止依赖结果集为空,不是靠WHERE条件硬限制
WHERE子句在递归成员里只控制当轮输出,不阻止下一轮执行。比如写WHERE depth ,其实是每轮都过滤,但数据库仍会尝试执行第6轮——直到关联不到数据才真正停止。这意味着:如果索引缺失,即使加了<code>WHERE,也可能因全表扫描反复执行,拖慢查询。
真正影响性能的是连接效率:
-
JOIN条件必须命中索引,例如ON d.parent_id = dt.id要求departments(parent_id)有索引 - 锚点查询也建议走索引,比如按
name = '技术部'查,最好给name建索引 - 默认递归深度上限是1000,超限会报
ERROR 3636 (HY000): Recursive query aborted after 1001 iterations,需调大cte_max_recursion_depth系统变量
路径拼接时NULL值会导致整段路径变NULL
递归中用CONCAT(dp.path, '/', d.name)时,只要dp.path是NULL(比如锚点没初始化),结果就是NULL,后续所有层级都会断掉。这不是bug,是MySQLCONCAT的语义:任一参数为NULL则返回NULL。
安全写法只有两种:
- 锚点强制非空:
SELECT id, name, parent_id, name AS path FROM departments WHERE parent_id IS NULL - 递归部分用
COALESCE兜底:CONCAT(COALESCE(dp.path, ''), '/', d.name)
别依赖SET sql_mode = 'PAD_CHAR_TO_FULL_LENGTH'之类全局设置,它对CONCAT无效。


















