通过多个“介于”条件优化查询

时间:2020-03-23 18:05:48

标签: sql postgresql select postgresql-performance

我有一个带有列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

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_repackpg_squeeze可能对此有所帮助。相关:

it's recommended可以使用最新的次要版本来使用任何主要版本。在撰写本文时(2020年2月13日发行),该值将为12.2。

相关问题