使用多个 "between" 条件优化查询

Optimize query with multiple "between" conditions

我有一个 table playgroundval,列 val 已编入索引。

我有一个范围列表[(min1, max1), (min2, max2), ... , (minN, maxN)] 我想 select 所有 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;使索引正常工作。

我担心"Nested Loop"阶段。可以吗?或者有更有效的方法将动态范围列表传递到查询中? 我的 postgres 版本是 12.1

您添加了更多信息,但还有更多相关信息。确切的 table 和索引定义、基数、数据分布、行大小统计信息、谓词中的范围数、table 的目的、写入模式……性能优化需要它可以获得的所有输入。

在黑暗中拍摄:对于非重叠范围,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}}');

相关:

  • Loop over array dimension in plpgsql

db<>fiddle here

行的物理顺序可能有很大帮助。如果行按顺序存储,则需要处理的数据页(多)少。取决于未公开的细节。内置 CLUSTER 或扩展 pg_repackpg_squeeze 可能对此有所帮助。相关:

  • Optimize Postgres timestamp query range

it's recommended 将最新可用的次要版本用于正在使用的任何主要版本。在撰写本文时为 12.2(2020 年 2 月 13 日发布)。