递归查询易因缺失索引或深度过大引发全表扫描、栈溢出或超时;应为id/parent_id建索引、避免非SARGable条件、设MAXRECURSION、防环、压测真实数据。

递归查询容易触发全表扫描
SQL 递归(比如 WITH RECURSIVE)在没有合适索引支撑时,每次迭代都可能回表查父/子记录,导致实际执行计划里出现多次 Seq Scan 或等价的全表遍历。尤其当树深度大、分支多,而连接字段(如 parent_id)没建索引时,性能会断崖式下降。
实操建议:
- 确保递归引用的关联字段(如
id和parent_id)都有单独或联合索引 - 避免在递归 CTE 中使用非 SARGable 条件(如
WHERE UPPER(name) = 'A'),这会让索引失效 - PostgreSQL 中可通过
EXPLAIN (ANALYZE, BUFFERS)观察每层迭代是否命中索引
深度过大引发栈溢出或超时
不同数据库对递归层级有硬性限制:PostgreSQL 默认 max_recursion_depth = 100(需通过 SET 调整),SQL Server 默认 100 层,超出直接报错 Maximum recursion exceeded;MySQL 8.0+ 虽无硬限制,但深度大时内存增长快,易被 max_execution_time 或 OOM kill。
实操建议:
- 显式加
MAXRECURSION 0(SQL Server)或SET search_path TO ...前设好深度上限,防止意外死循环 - 用
LIMIT或WHERE level 主动截断(PostgreSQL / MySQL),别依赖默认值 - 若业务允许,把深树拆成“分批拉取”:先查第 1 层,再查第 2 层……用应用层控制,避开单次大递归
重复计算导致结果膨胀和内存飙升
递归 CTE 默认是 UNION ALL 语义,如果父子关系存在环(如 A→B→C→A),或数据本身有冗余路径(如组织架构中某人同时属两个部门),就会无限生成重复行,最终返回几万甚至百万行——而你只想要 10 个节点。
实操建议:
- 务必在递归部分加入防环逻辑,例如 PostgreSQL 中用
ARRAY[...] @> ARRAY[id]检查路径是否已含当前 id - 避免在递归分支中做
JOIN多张大表,尤其是未加过滤条件的;先收窄 ID 集合,再补关联字段 - 用
SELECT DISTINCT ON (id)或外层GROUP BY去重,但注意这不能替代防环,只是补救











