递归查询的失控风险
递归查询用于处理层级数据:组织架构、目录树、物料清单、多级分类。写法上分成两部分,先取根节点作为起点,再用连接把子节点一层层接上。风险在于数据本身成环或者连接条件写错时,递归永不终止,把数据库连接和内存一起耗尽。MySQL 内置了递归深度上限作为兜底,PostgreSQL 默认没有这层保护,必须自己在语句里加深度判断或路径判断。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL 12 及以上 |
|---|---|
| MySQL 保护 | 递归深度参数默认为 1000,超出后语句以递归中止报错结束 |
| PostgreSQL 保护 | 默认无深度上限,需要语句内自行加深度条件 |
| 环检测 | PostgreSQL 14 起支持专门的环检测子句,自动标记并截断 |
| 通用做法 | 递归项里累加层级数并限制上限,同时对脏数据做定期巡检 |
典型使用场景
- 组织架构展开:从某个部门出发列出所有下级部门与人员
- 目录或分类树:按父子字段拼出完整层级路径
- 物料清单展开:算出一件成品需要的全部下级零件数量
- 影响面分析:给定一个模块,找出所有依赖它的上游服务
- 层级路径去重:把树的每一层结果拼成可读路径,用于展示或导出
加深度限制的通用写法
加深度限制的通用写法
WITH RECURSIVE org AS (
SELECT id, manager_id, 1 AS lvl -- 起点:没有上级的根节点
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.manager_id, o.lvl + 1 -- 递归项:逐层向下展开
FROM employees e
JOIN org o ON e.manager_id = o.id
WHERE o.lvl < 10 -- 深度上限,防止无限递归
)
SELECT * FROM org;
-- PostgreSQL 14 起的环检测写法
WITH RECURSIVE walk AS (
SELECT id, parent_id FROM nodes WHERE parent_id IS NULL
UNION ALL
SELECT n.id, n.parent_id FROM nodes n JOIN walk w ON n.parent_id = w.id
) CYCLE id SET is_cycle USING path -- 自动记录路径并标记成环
SELECT * FROM walk WHERE NOT is_cycle;两层保护对照
| 保护手段 | MySQL 行为 | PostgreSQL 行为 |
|---|---|---|
| 深度上限参数 | 有默认上限,超出报错中止 | 无默认上限,需自己写在语句里 |
| 环检测语法 | 无专门语法,用路径条件代替 | 14 起支持专门的环检测子句 |
| 超限表现 | 语句失败并给出迭代次数提示 | 不限制时会一直跑,直到资源耗尽 |
| 推荐做法 | 深度条件与路径去重一起用 | 深度条件为主,新版可用环检测 |
看到「递归超过迭代次数」这类报错时,第一反应应该是查数据里有没有环、连接条件是不是写反了,而不是直接把深度参数调大。调大参数只是把报错推迟,真正的环仍然存在,而且每轮迭代都在消耗资源。
如果为了记录路径而在递归里不断拼接字符串,内存占用会随深度线性增长。层级很深的树要谨慎,必要时只保留必要的路径节点或改成逐层落库。