[问题记录.pg]使用 WITH RECURSIVE 时遇到问题数据,引发死循环。如何避免?

本文介绍了一个项目中出现的服务器内存和CPU使用率异常增高的问题,并通过分析发现是由递归SQL查询引起的死循环。文中详细解释了如何复现该问题,并提供了一种改进SQL的方法来避免死循环的发生。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题现象:

某项目偶发出现服务器内存和CPU涨爆的问题。

问题排查:

通过抓取问题出现时的dump,定位到是在执行一句pg库的sql。而这段sql是递归查询,通过参数检查数据,确认是出现了死循环。

问题解决:

1、修正问题数据

2、完善sql,应对递归循环很多层的问题


顺道把验证案例备忘在这里

1、创建测试表并插入验证数据

注意,此时数据不会出现死循环

 CREATE TABLE test_recursive (id int,pid int);
 
 INSERT INTO test_recursive VALUES (1 , NULL) ,(2, 1) ,(3 , 2) ,(4 , 3) ,(5 , 4) ,(6 , 5) ,(7 , 6);
 

2、未处理死循环的执行SQL

 WITH RECURSIVE x(id, pid, root) AS ( 
 	SELECT id, pid, id as root
      FROM test_recursive 
     WHERE id=1
    UNION ALL
    SELECT t.id, t.pid, x.root
      FROM test_recursive t, x
     WHERE x.id = t.pid
)
 SELECT id, pid, root
   FROM x  
  ORDER BY id;

3、写入问题数据,引发死循环

 update test_recursive set pid = 7 where id = 1;

4、完善后的SQL(遇到死循环会终止)

 WITH RECURSIVE x(id, pid, root, level, path, cycle) AS ( 
 	SELECT id, pid, id as root, 1, array[id], false
      FROM test_recursive 
     WHERE id=1
    UNION ALL
    SELECT t.id, t.pid, x.root, x.level+1, x.path||t.id, t.id=ANY(path)
      FROM test_recursive t, x
     WHERE x.id = t.pid and not x.cycle 
)
 SELECT id, pid, root, level, array_to_string(path,'\') AS path, cycle
   FROM x 
  where not x.cycle 
  ORDER BY id;

<think>好的,用户的问题是在Oracle中使用WITH RECURSIVE进行递归查询,并将结果用逗号分隔。首先,我需要确认Oracle是否支持WITH RECURSIVE语法。根据之前的知识,Oracle的递归查询通常使用CONNECT BY和START WITH子句,而WITH子句在Oracle中被称为通用表表达式(CTE),但递归CTE的语法可能有所不同。 查阅引用资料,引用[4]提到Oracle使用CONNECT BY进行递归查询,而MySQL 8.0以上使用WITH RECURSIVE。不过引用[3]指出Oracle的WITH子句可以优化性能,但可能不直接支持递归。因此,用户可能混淆了语法,需要明确Oracle的正确递归写法。 接下来,用户希望将结果以逗号分隔。Oracle中可以使用LISTAGG函数来实现这一点。所以,正确的步骤应该是使用CONNECT BY进行递归查询,然后使用LISTAGG聚合结果。 现在需要编写示例SQL。假设有一个表EMPLOYEE,包含ID和MANAGER_ID字段。递归查询所有下属,然后用逗号分隔。首先,使用CONNECT BY获取层级关系,然后使用SYS_CONNECT_BY_PATH生成路径,最后用LISTAGG聚合。 检查语法是否正确,确保CONNECT BY和START WITH的正确使用,以及LISTAGG的参数是否正确。可能需要子查询先获取递归结果,再聚合。同,注意处理循环和层级限制,比如NOCYCLE和LEVEL <= 10。 最后,验证示例是否符合用户需求,并确保回答结构清晰,分步骤说明,并附上相关问题。</think>在 Oracle 数据库中,递归查询建议使用 `CONNECT BY` 语法而非 `WITH RECURSIVE`。但若需要实现类似功能并格式化结果,可通过以下步骤实现: ### 1. 递归查询实现 使用 `CONNECT BY` 实现层级查询,结合 `SYS_CONNECT_BY_PATH` 函数生成路径: ```sql SELECT ID, SYS_CONNECT_BY_PATH(name, ',') AS path FROM employee WHERE CONNECT_BY_ISLEAF = 1 -- 仅保留叶子节点路径 START WITH manager_id IS NULL CONNECT BY NOCYCLE PRIOR id = manager_id ``` ### 2. 结果合并与分隔 使用 `LISTAGG` 函数将多行结果合并为逗号分隔字符串: ```sql SELECT LISTAGG(name, ',') WITHIN GROUP (ORDER BY id) AS concat_names FROM ( SELECT name, id FROM employee START WITH manager_id IS NULL CONNECT BY PRIOR id = manager_id ) ``` ### 组合示例 若需递归查询某节点的所有子节点并合并: ```sql WITH hierarchy_cte AS ( SELECT name FROM employee START WITH id = 1 -- 指定根节点 CONNECT BY PRIOR id = manager_id ) SELECT LISTAGG(name, ',') AS all_children FROM hierarchy_cte ``` ### 注意事项 1. `NOCYCLE` 参数可防止循环引用报错 2. `LEVEL` 伪列可控制递归深度(如 `WHERE LEVEL <= 5`) 3. 性能优化建议对 `manager_id` 字段建立索引[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

俊哥V

这是个嘛?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值