MySQL 8.0+才支持WITH RECURSIVE,5.7及以下版本不识别该语法;它是唯一原生递归方案,因MySQL缺乏CONNECT BY等替代机制,且自连接难以应对深度不确定的树形结构。

MySQL 8.0+ 才支持 WITH RECURSIVE,低于这个版本直接写会报错 ERROR 1064 —— 别在 5.7 上试,它真不认这个语法。
为什么 WITH RECURSIVE 是唯一靠谱的原生递归方案
MySQL 没有 CONNECT BY(Oracle)或 hierarchyid(SQL Server),也没内置路径字段函数。靠自连接写 N 层关联?3 层还行,5 层就难维护,且无法应对深度不确定的树形结构。而 WITH RECURSIVE 能动态展开任意深度,语义清晰,执行计划也相对可控。
常见错误现象:ERROR 3638: Recursive query aborted after 1001 iterations —— 这是防死循环的保护机制,不是 bug,是配置项没调。
- 必须显式定义锚点(anchor)和递归成员(recursive member),且二者
UNION ALL合并,不能用UNION - 递归成员中引用 CTE 名称必须出现在
FROM子句右侧,不能在子查询或JOIN的左侧 - 递归字段类型要和锚点严格一致(比如
id是BIGINT,递归里就不能转成VARCHAR)
WITH RECURSIVE 查询模板与关键参数
假设有一张 categories 表,含 id、name、parent_id 字段,查某个分类及其所有子类:
WITH RECURSIVE category_tree AS ( -- 锚点:起始节点 SELECT id, name, parent_id, 0 AS level FROM categories WHERE id = 1 <p>UNION ALL</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill10796" title="MySQL8 CRM Schema Design Expert"><img src="https://img.php.cn/upload/skill/001/246/273/6adaf20dbae7c507.jpg" alt="MySQL8 CRM Schema Design Expert" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill10796" title="MySQL8 CRM Schema Design Expert" class="overflowclass">MySQL8 CRM Schema Design Expert</a> <p class="overflowclass">面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。</p> </div> <a rel="nofollow" href="/xiazai/skill10796" title="MySQL8 CRM Schema Design Expert" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div><p>-- 递归成员:找下一层子节点 SELECT c.id, c.name, c.parent_id, ct.level + 1 FROM categories c INNER JOIN category_tree ct ON c.parent_id = ct.id ) SELECT * FROM category_tree ORDER BY level, id;</p>
几个关键点:
-
level字段非必需,但强烈建议加上,便于排序、限深、前端渲染缩进 - 递归深度默认上限是 1000,超了就报错;可通过
SET SESSION cte_max_recursion_depth = 2000;临时调高(注意是 session 级,不是全局) - 如果想查“某节点的所有祖先”,把
INNER JOIN条件改成c.id = ct.parent_id,锚点用子节点 ID,方向就反过来了
性能和索引怎么配才不慢
递归查询本质是多次等值查找,parent_id 字段有没有索引,直接影响每次 JOIN 的速度。没有索引时,每层都要全表扫,N 层就是 N × 全表扫描。
- 必须给
parent_id加索引:CREATE INDEX idx_parent_id ON categories(parent_id); - 若常按
name搜索起始节点,锚点部分的WHERE id = ?已走主键,不用额外优化;但如果是WHERE name = 'xxx',就得给name加索引 - 避免在递归成员里用函数包裹连接字段,比如
ON CAST(c.parent_id AS CHAR) = ct.id_str—— 会导致索引失效 - 大数据量下,慎用
ORDER BY在最终结果上排序;可考虑在应用层收齐数据后再排
真正容易被忽略的是递归终止条件完全依赖数据质量:如果存在环(A → B → C → A),即使设了 cte_max_recursion_depth,也可能在到达上限前就因逻辑错误返回不完整结果。上线前务必检查 parent_id 是否可能指向自身或形成闭环。










