【踩坑-Postgresql数据库】那年踩过的坑爹问题合集

1 篇文章 0 订阅
1 篇文章 0 订阅

嵌套使用group,子查询得到的结果集被执行计划取消

业务场景

先说下我遇到的业务场景(觉得枯燥可以跳过直接看后面的SQL)

我现在有两个表:

一个树结构的单表-机构表 dept_table【数据量约100条】。

一个保存故障设备解决历时的单表-设备故障解决历时表(实际上是多表联查得到的数据,但这里当作单表来说明,下面简称历时表)time_table【数据量约20000条】。

它们的关系是一对多,一个机构下存在多个设备。

现在我的任务是:统计每个机构故障设备的已解决历时和未解决历时平均值(需要包含下属机构的数据)。

我的做法是:将机构表 dept_table 和历时表 time_table 进行左连接,然后根据机构id 进行 group 分组,得出每个机构的平均历时数据(但此时不包含下属机构的数据)作为结果集 dept_temp_table,之后再对机构表 dept2_table 进行一次自连接后 group 分组,即可得出包含下属机构的平均历时数据。

这时,坑爹的事情来了,通过查看执行计划我发现,在自连接的时候居然忽略了我的临时表 dept_temp_table,而采用了未分组的原表 dept_table 进行表连接(要知道在进行左连接之后 dept_table 已经从一百条数据增大到了上万条,如果不进行分组处理而是直接自连接,成本是乘以百倍的),最后的结果就是原本一秒不用的SQL,居然耗费了十多秒。(我在使用MySQL的时候并没有这个问题)

模拟场景的SQL

由于项目用到的SQL比较冗长繁杂,也属于私密信息不方便公开,所以下面我用了一个比较简单的例子来证明这个结果集被取消的现象

	EXPLAIN ( ANALYSE, VERBOSE, TIMING, costs, buffers, timing ) 
	SELECT 
		sd4.dept_id, AVG(test_avg) AS test_avg 
	FROM 
		(
			SELECT
				dept_id
			FROM
				sys_dept sdd
			WHERE
					CAST ( 1 AS TEXT ) = ANY ( string_to_array( ancestors, ',' ) ) 	-- 权限控制,只能查询自己所属部门以及下属部门的数据	
	) sd4 
	LEFT JOIN (
				-- 下面的SQL将得到所有机构的设备故障历时平均值,构成结果集sd5(不包含下属部门)
				SELECT
					sd.dept_id,sd.ancestors
					, AVG(vai.belong_dept_id) AS test_avg -- 求出平均值
				FROM
					sys_dept sd INNER JOIN -- 这里的表连接用于获取每个机构的数据集
					( 
						SELECT 
							vai2.belong_dept_id, vai2.inst_id
						FROM
							sys_dept sd2
						LEFT JOIN 
							v_am_inst vai2 
						ON sd2.dept_id = vai2.belong_dept_id 
					) vai -- 拿到数据结果集,这里只是做一个模拟,随便拿个数值而已
					ON vai.belong_dept_id = sd.dept_id
				GROUP BY
					sd.dept_id,
					sd.ancestors
	 ) sd5 ON CAST ( sd4.dept_id AS TEXT ) = ANY ( string_to_array( sd5.ancestors, ',' ) ) -- 通过祖级列表进行左连接,获得下属部门的数据
	GROUP BY 
		sd4.dept_id
		

运行得到执行计划如下:

我的解决方法是给子查询的结果集 sd5 再套一层毫无意义的子查询并group by。如下:

EXPLAIN ( ANALYSE, VERBOSE, TIMING, costs, buffers, timing ) 
	SELECT 
		sd4.dept_id, AVG(test_avg) AS test_avg 
	FROM 
		(
			SELECT
				dept_id
			FROM
				sys_dept sdd
			WHERE
					CAST ( 1 AS TEXT ) = ANY ( string_to_array( ancestors, ',' ) ) 	-- 权限控制,只能查询自己所属部门以及下属部门的数据	
	) sd4 
	LEFT JOIN (
		SELECT -- 毫无意义的查询,但这种行为可以让执行计划引用我们内部的子查询。。
			dept_id, ancestors, test_avg 
		FROM
			(
				-- 下面的SQL将得到所有机构的设备故障历时平均值,构成结果集sd5(不包含下属部门)
				SELECT
					sd.dept_id,sd.ancestors
					, AVG(vai.belong_dept_id) AS test_avg -- 求出平均值
				FROM
					sys_dept sd INNER JOIN -- 这里的表连接用于获取每个机构的数据集
					( 
						SELECT 
							vai2.belong_dept_id, sd2.dept_id
						FROM
							sys_dept sd2
						LEFT JOIN 
							v_am_inst vai2 
						ON sd2.dept_id = vai2.belong_dept_id 
					) vai -- 拿到数据结果集,这里只是做一个模拟,随便拿个数值而已
					ON vai.belong_dept_id = sd.dept_id
				GROUP BY
					sd.dept_id,
					vai.dept_id, -- 这一步至关重要,在group by后面必须同时引用到两个临时表的字段,否则无效
					sd.ancestors
			) sd5 
		GROUP BY dept_id, ancestors, test_avg -- 毫无意义的分组,但这种行为可以让执行计划引用我们内部的子查询。。
	 ) sd6 ON CAST ( sd4.dept_id AS TEXT ) = ANY ( string_to_array( sd6.ancestors, ',' ) ) -- 通过祖级列表进行左连接,获得下属部门的数据
	GROUP BY 
		sd4.dept_id
		

运行得到执行计划如下:

实际上在得出这个结论之前,我进行过多次测试,发现还有一些行为同样可以正常引用到子查询,比方说第 11 行的权限控制过滤SQL,如果去掉这个过滤条件,则子查询还是可以被引用到的。还有数据量的关系,如果得到的数据集行数少于6700(这个数量我不是很肯定,是用 20 条数据的表和 335 条数据的表进行连接测出来的)则子查询同样可以被引用到。

归根结底还是对PostgreSQL数据库的执行计划完全不懂,如果有前辈知道其中原理,劳烦在评论区里点拨一二,十分感谢!!

行为记录

解决耗时:三天

实际耗时:约18小时

价值:中等

具有参考价值的外链:(以下外链未必与本次问题的解决直接相关,但都是我在解决途中记录下来的)

PostgreSQL执行计划的解释_pgsql seq scan on t-CSDN博客

postgresql -- 执行计划_recheck cond-CSDN博客

PostgreSQL 不能索引的10个原因_pg索引失效_snowyar的博客-CSDN博客3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值