我有一个带有列playground
的表val
,索引了列val
。
我有一个范围列表[(min1, max1), (min2, max2), ... , (minN, maxN)]
并且我想选择val
中所有适合这些范围的所有行。
例如我的范围如下所示:[(1,5), (20,25), (200,400)]
这是提取相应行的简单查询:
select p.*
from playground p
where (val between 1 AND 5) or (val between 20 and 25) or
(val between 200 and 400);
这里的问题是该范围列表是动态的,我的应用程序生成了该范围并将其与查询一起发送到postgres。
我试图重写查询以接受动态范围列表:
select p.*
from playground p,
unnest(ARRAY [(1, 5),(20, 25),(200, 400)]) as r(min_val INT, max_val INT)
where p.val between r.min_val and r.max_val;
它提取相同的行,但是我不知道这是一个有效的查询吗?
这是第一个查询的解释:
Bitmap Heap Scan on playground p (cost=12.43..16.45 rows=1 width=36) (actual time=0.017..0.018 rows=4 loops=1)
Recheck Cond: (((val >= 1) AND (val <= 5)) OR ((val >= 20) AND (val <= 25)) OR ((val >= 200) AND (val <= 400)))
Heap Blocks: exact=1
-> BitmapOr (cost=12.43..12.43 rows=1 width=0) (actual time=0.012..0.012 rows=0 loops=1)
-> Bitmap Index Scan on playground_val_index (cost=0.00..4.14 rows=1 width=0) (actual time=0.010..0.010 rows=3 loops=1)
Index Cond: ((val >= 1) AND (val <= 5))
-> Bitmap Index Scan on playground_val_index (cost=0.00..4.14 rows=1 width=0) (actual time=0.001..0.001 rows=0 loops=1)
Index Cond: ((val >= 20) AND (val <= 25))
-> Bitmap Index Scan on playground_val_index (cost=0.00..4.14 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
Index Cond: ((val >= 200) AND (val <= 400))
Planning Time: 0.071 ms
Execution Time: 0.057 ms
这是第二个解释:
Nested Loop (cost=0.14..12.52 rows=2 width=36) (actual time=0.033..0.065 rows=4 loops=1)
-> Function Scan on unnest r (cost=0.00..0.03 rows=3 width=8) (actual time=0.011..0.012 rows=3 loops=1)
-> Index Scan using playground_val_index on playground p (cost=0.13..4.15 rows=1 width=36) (actual time=0.008..0.015 rows=1 loops=3)
Index Cond: ((val >= r.min_val) AND (val <= r.max_val))
Planning Time: 0.148 ms
Execution Time: 0.714 ms
注意:在这两种情况下,我都做了set enable_seqscan = false;
才能使索引正常工作。
我担心“嵌套循环”阶段。可以吗还是有更有效的方法将动态范围列表传递到查询中?
我的postgres版本是12.1
答案 0 :(得分:1)
您添加了更多信息,但还有更多相关信息。精确的表和索引定义,基数,数据分布,行大小统计信息,谓词的范围数,表的用途,写入模式等...性能优化需要它可以获得的所有输入。
暗中射击:在不重叠范围的情况下,UNION ALL
查询可能可提供最佳性能:
SELECT * FROM playground WHERE val BETWEEN 1 AND 5
UNION ALL
SELECT * FROM playground WHERE val BETWEEN 20 AND 25
UNION ALL
SELECT * FROM playground WHERE val BETWEEN 200 AND 400;
我们知道范围不会重叠,但Postgres不会重叠,因此在尝试时它必须做额外的工作。此查询应避免第一个计划的BitmapOr
和第二个计划的Nested Loop
。只需获取每个范围并附加到输出即可。应该产生如下计划:
Append (cost=0.13..24.50 rows=3 width=40)
-> Index Scan using playground_val_idx on playground (cost=0.13..8.15 rows=1 width=40)
Index Cond: ((val >= 1) AND (val <= 5))
-> Index Scan using playground_val_idx on playground playground_1 (cost=0.13..8.15 rows=1 width=40)
Index Cond: ((val >= 20) AND (val <= 25))
-> Index Scan using playground_val_idx on playground playground_2 (cost=0.13..8.15 rows=1 width=40)
Index Cond: ((val >= 200) AND (val <= 400))
此外,每个子SELECT
都将基于给定范围的实际统计信息,而不是一般估计,即使是更长的范围列表也是如此。参见(推荐!):
您可以在客户端中生成查询,也可以编写服务器端函数来生成和执行动态SQL(适用于已知的结果类型)。
您甚至可以使用LOOP
(通常效率较低,但这可能是一个例外)来测试服务器端功能:
CREATE OR REPLACE FUNCTION foo(_ranges int[])
RETURNS SETOF playground LANGUAGE plpgsql PARALLEL SAFE STABLE AS
$func$
DECLARE
_range int[];
BEGIN
FOREACH _range SLICE 1 IN ARRAY _ranges
LOOP
RETURN QUERY
SELECT * FROM playground WHERE val BETWEEN _range[1] AND _range[2];
END LOOP;
END
$func$;
开销可能无法支付通话中的几个范围。但是打电话非常方便,如果没有其他要求:
SELECT * FROM foo('{{1,5},{20,25},{200,400}}');
相关:
db <>提琴here
行的物理顺序可能对很有帮助。如果按顺序存储行,则需要处理的数据页面要少得多。取决于未公开的细节。内置CLUSTER
或扩展名pg_repack
或pg_squeeze
可能对此有所帮助。相关:
和it's recommended可以使用最新的次要版本来使用任何主要版本。在撰写本文时(2020年2月13日发行),该值将为12.2。