with recursive必须定义终止条件,否则会无限循环;其由非递归项(锚点)和递归项组成,递归项需含自然收敛逻辑(如level),否则导致超时、中断或“stack depth limit exceeded”错误。

WITH RECURSIVE 递归查询必须定义终止条件,否则会无限循环
PostgreSQL、SQLite、SQL Server(自2017)、MySQL(8.0+)都支持 WITH RECURSIVE,但只要没写对终止逻辑,查询就会卡死或报错。常见现象是执行超时、连接中断,或者 PostgreSQL 报 ERROR: stack depth limit exceeded。
递归 CTE 分两部分:非递归项(锚点)和递归项(用自身引用自身)。关键在递归项里必须有能自然收敛的判断,比如 level 、<code>parent_id != id 或路径字符串不重复。
- 锚点不能为空结果集,否则整个 CTE 返回空 —— 检查
WHERE条件是否误筛掉根节点 - 递归项中禁止使用聚合函数、窗口函数、
GROUP BY、ORDER BY(除非在最外层) - MySQL 对递归深度默认限制为 1000,可通过
SET SESSION cte_max_recursion_depth = 2000调整 - PostgreSQL 的
max_recursion_depth默认无限制,靠栈深度硬控,更易爆栈
树形结构查询要小心自环与多父节点场景
真实业务数据常存在脏数据:某个节点的 parent_id 指向自己,或一个子节点被多个父节点引用(如 DAG 图)。这时单纯靠 parent_id = t.id 会漏数据或死循环。
典型建模是单根树(parent_id IS NULL 表示根),但一旦出现自环(id = parent_id),递归就停不下来;若允许多父,标准树查询就不适用,得改用图遍历思路。
- 加防护:在递归分支中过滤自环,例如
AND t.id != t.parent_id - 防重复路径:用
ARRAY[cte.id](PostgreSQL)或字符串拼接(如CONCAT(path, ',', t.id))记录已访问节点,再用NOT LIKE或@path NOT REGEXP CONCAT('(^|,)t.id(,|$)')判断 - MySQL 不支持数组,可用
CONCAT(',', path, ',') NOT LIKE '%,t.id,%'做简单去重(注意开头结尾加逗号防误匹配) - 如果业务本质是 DAG(如依赖关系),别硬套树模型,考虑用拓扑排序或额外中间表预计算层级
层级展开后排序依赖 path 字段,ORDER BY 不能直接按 level
只靠 ORDER BY level 只能实现广度优先(BFS)展示,但多数前端需要的是“缩进式树形列表”,即深度优先(DFS)顺序:根 → 子1 → 孙1 → 孙2 → 子2… 这必须靠构造可排序的路径标识。
不同数据库构造方式不同:PostgreSQL 推荐用 ARRAY 类型拼接 ID 序列,MySQL 只能用定长字符串(如 LPAD(id, 10, '0'))拼接,否则排序会错乱(比如 1,10,2 按字符串排成 1,10,2 而非 1,2,10)。
- PostgreSQL 示例:递归中
path = t.id || cte.path(前缀追加),最后ORDER BY path - MySQL 示例:锚点设
path = LPAD(id, 10, '0'),递归中path = CONCAT(cte.path, LPAD(t.id, 10, '0')),再ORDER BY path - SQLite 支持
printf('%010d', id),用法类似 MySQL - 避免用
CONCAT(id, '.', ...)直接拼数字,会导致字典序错乱
图路径查询需限制最大跳数并显式剪枝
查“从 A 到 B 的最短路径”或“所有可达节点”时,WITH RECURSIVE 天然适合,但图可能稠密,不加约束会爆炸式生成中间路径。
比如社交关系中查 4 度好友,若不限制层级,1000 个好友每人再带 100 个好友,第三层就到百万级,第四层破亿 —— 查询还没跑完内存先爆了。
- 强制加跳数限制:
WHERE cte.depth 是底线,不要依赖应用层截断 - 到达目标即停:在递归项中加
AND t.id != :target_id,并在外层WHERE id = :target_id筛最终结果,避免生成无效路径 - PostgreSQL 可用
UNION替代UNION ALL自动去重,但代价高;更推荐在路径字符串里做重复节点检查 - 如果只要最短路径长度,别返回完整路径字段,只维护
depth,减少传输和排序开销
递归查询不是银弹。层级深、图稠密、数据脏 —— 这三类情况最容易让 WITH RECURSIVE 从利器变阻塞源。动手前先看数据分布,再决定是预计算、加索引,还是换图数据库。










