PostgreSQL 递归死循环案例及解法

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 背景 PostgreSQL 提供的递归语法是很棒的,例如可用来解决树形查询的问题,解决Oracle用户 connect by的语法兼容性。 请参考https://yq.aliyun.com/articles/54657 但是如果参与递归查询的数据集有问题,例如数据打结的问题。则会导致递

背景

PostgreSQL 提供的递归语法是很棒的,例如可用来解决树形查询的问题,解决Oracle用户 connect by的语法兼容性。

请参考
https://yq.aliyun.com/articles/54657

但是如果参与递归查询的数据集有问题,例如数据打结的问题。则会导致递归死循环,可能导致临时文件暴增,把空间占满,影响业务。

案例

假设c1,c2是上下级关系,c2是c1的上级ID。
创建测试表如下

create table test(c1 int, c2 int, info text);
create index idx_test_01 on test(c1);
create index idx_test_02 on test(c2);

插入一组测试数据,其中(1,1,'test')是个结,如果用递归查询的话,会导致无法退出循环。

insert into test values 
(9,8,'test'), 
(8,7,'test'), 
(7,6,'test'), 
(6,5,'test'), 
(5,4,'test'), 
(4,3,'test'), 
(3,2,'test'), 
(2,1,'test'), 
(1,1,'test');

递归查询,从c1=9开始往上检索,到1之后会一直往下走,无法终结。

with recursive t(c1,c2,info) as (
  select * from test where c1=9 
  union all 
  select t2.* from test t2 join t on (t.c2 =t2.c1) 
) 
select count(*) from t;

可以在数据库的临时文件目录,看到不停增长的临时文件。

total 96M
-rw------- 1 digoal digoal 89M Jul 23 20:07 pgsql_tmp8997.5

一段时间后
...
-rw------- 1 digoal digoal 575M Jul 23 20:09 pgsql_tmp8997.5

继续产生
total 2.5G
-rw------- 1 digoal digoal 1.0G Jul 23 20:10 pgsql_tmp8997.5
-rw------- 1 digoal digoal 1.0G Jul 23 20:14 pgsql_tmp8997.6
-rw------- 1 digoal digoal 435M Jul 23 20:15 pgsql_tmp8997.7

如何解决死循环的问题

临时文件相关的数据库参数介绍

#temp_buffers = 8MB                     # min 800kB
临时空间buffer大小
#
#temp_file_limit = -1                   # limits per-session temp file space
                                        # in kB, or -1 for no limit
单个会话最多允许产生多少临时文件
#
log_temp_files = 102400                 # log temporary files equal or larger
                                        # than the specified size in kilobytes;
                                        # -1 disables, 0 logs all temp files
当临时文件使用超过多少时,记录日志  
但是别被它误解,是QUERY结束的时候记录的,中途不记录。  
可以通过改内核实现阶段性的记录。  
#
#temp_tablespaces = ''                  # a list of tablespace names, '' uses
                                        # only default tablespace
可以指定临时目录的表空间,默认是default tablespace
#

看完以上几个参数,大家应该心里有数了。
通过设置temp_file_limit即可限制当前会话允许使用的最大临时空间。

测试
手工退出刚才的死循环QUERY

postgres=# with recursive t(c1,c2,info) as (select * from test where c1=9 union all select t2.* from test t2 join t on (t.c2 =t2.c1) ) select count(*) from t;
^CCancel request sent
ERROR:  57014: canceling statement due to user request
LOCATION:  ProcessInterrupts, postgres.c:2988

QUERY退出后才记录临时文件的日志,社区版本的问题,没有阶段性记录临时空间的使用

2016-07-23 20:17:42.227 CST,"postgres","postgres",8997,"[local]",5793598b.2325,10,"SELECT",2016-07-23 19:48:27 CST,2/2490781,0,ERROR,57014,"canceling statement due to user request",,,,,,"with recursive t(c1,c2,info) as (select * from test where c1=9 union all select t2.* from test t2 join t on (t.c2 =t2.c1) ) select count(*) from t;",,,"psql"
2016-07-23 20:17:43.521 CST,"postgres","postgres",8997,"[local]",5793598b.2325,11,"SELECT",2016-07-23 19:48:27 CST,2/0,0,LOG,00000,"temporary file: path ""base/pgsql_tmp/pgsql_tmp8997.7"", size 1073741824",,,,,,,,,"psql"
2016-07-23 20:17:43.747 CST,"postgres","postgres",8997,"[local]",5793598b.2325,12,"SELECT",2016-07-23 19:48:27 CST,2/0,0,LOG,00000,"temporary file: path ""base/pgsql_tmp/pgsql_tmp8997.6"", size 1073741824",,,,,,,,,"psql"
2016-07-23 20:17:43.991 CST,"postgres","postgres",8997,"[local]",5793598b.2325,13,"SELECT",2016-07-23 19:48:27 CST,2/0,0,LOG,00000,"temporary file: path ""base/pgsql_tmp/pgsql_tmp8997.5"", size 1073741824",,,,,,,,,"psql"

设置会话的临时文件使用为10MB,继续测试,可以看到效果很明显

死循环的问题解决了

postgres=# set temp_file_limit='10MB';
SET
postgres=# with recursive t(c1,c2,info) as (select * from test where c1=9 union all select t2.* from test t2 join t on (t.c2 =t2.c1) ) select count(*) from t;
ERROR:  53400: temporary file size exceeds temp_file_limit (10240kB)
LOCATION:  FileWrite, fd.c:1491

RDS PG内核改进建议

  1. 建议可以动态设置temp_file_limit,根据实际的剩余空间设置反馈机制,保证有足够的剩余空间,不至于TEMP文件把空间全部撑爆。
  2. 可以将会话级别的临时空间限制,改为分组限制。
    例如group a 允许使用100MB,group b允许使用1GB。

又或者是用户或数据库级别的限制。
借鉴Greenplum的resource queue的管理手段,把资源控制做起来也是一种方法。
《Greenplum 资源隔离的原理与源码分析》
https://yq.aliyun.com/articles/57763

  1. 阶段性的记录临时文件的日志,而不是QUERY结束时记录

小结

  1. 用户使用递归语句时一定要注意防止死循环,通过设置会话级别的temp_file_limit可以预防,还有一种方法是使用pg_hint_plan,在语句中使用HINT,例如:
/*+ 
  Set (temp_file_limit='10MB')
*/
with recursive t(c1,c2,info) as (select * from test where c1=9 union all select t2.* from test t2 join t on (t.c2 =t2.c1) ) select count(*) from t;
  1. 临时文件会在QUERY结束后自动清理。
  2. 数据库启动时,startup进程也会清理temp文件。

祝大家玩得开心,欢迎随时来 阿里云促膝长谈 业务需求 ,恭候光临。

阿里云的小伙伴们加油,努力做 最贴地气的云数据库

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
9月前
|
存储 NoSQL 关系型数据库
PostgreSQL 12的可拔插存储引擎--表访问方法以及bloackholes案例
PostgreSQL 12的可拔插存储引擎--表访问方法以及bloackholes案例
121 0
|
9月前
|
关系型数据库 PostgreSQL
postgresql中关联多表递归查询,并分组计数、求和
postgresql中关联多表递归查询,并分组计数、求和
|
Oracle 关系型数据库 PostgreSQL
|
关系型数据库 PostgreSQL
《阿里云PostgreSQL、PPAS、HDB for PG生态、产品、案例、实践》电子版地址
阿里云PostgreSQL、PPAS、HDB for PG生态、产品、案例、实践
91 0
《阿里云PostgreSQL、PPAS、HDB for PG生态、产品、案例、实践》电子版地址
|
SQL 存储 移动开发
PostgreSQL psql的使用,SQL语法,数据类型,递归SQL用法(四)|学习笔记
快速学习3 PostgreSQL psql的使用,SQL语法,数据类型,递归SQL用法(四)
382 0
 PostgreSQL psql的使用,SQL语法,数据类型,递归SQL用法(四)|学习笔记
|
SQL 关系型数据库 数据库
3 PostgreSQL psql的使用,SQL语法,数据类型,递归SQL用法(三)|学习笔记
快速学习3 PostgreSQL psql的使用,SQL语法,数据类型,递归SQL用法(三)
304 0
|
机器学习/深度学习 存储 JSON
阿里云PostgreSQL_案例介绍4 | 学习笔记
简介:快速学习阿里云PostgreSQL_案例介绍4
114 0
阿里云PostgreSQL_案例介绍4 | 学习笔记
|
SQL 容灾 NoSQL
阿里云 PostgreSQL_案例介绍3 | 学习笔记
简介:快速学习阿里云 PostgreSQL_案例介绍3
139 0
阿里云 PostgreSQL_案例介绍3 | 学习笔记
|
存储 运维 Cloud Native
阿里云PolarDB数据库获评 “人民匠心2021年度推荐技术案例 ”
PolarDB凭借在国产自研数据库技术领域的重要创新和规模化成功应用入选
241 0
阿里云PolarDB数据库获评 “人民匠心2021年度推荐技术案例 ”

相关产品

  • 云原生数据库 PolarDB