使用多个 "between" 条件优化查询
Optimize query with multiple "between" conditions
我有一个 table playground
列 val
,列 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_repack
或 pg_squeeze
可能对此有所帮助。相关:
- Optimize Postgres timestamp query range
和 it's recommended 将最新可用的次要版本用于正在使用的任何主要版本。在撰写本文时为 12.2(2020 年 2 月 13 日发布)。
我有一个 table playground
列 val
,列 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_repack
或 pg_squeeze
可能对此有所帮助。相关:
- Optimize Postgres timestamp query range
和 it's recommended 将最新可用的次要版本用于正在使用的任何主要版本。在撰写本文时为 12.2(2020 年 2 月 13 日发布)。