什么时候`my_variable IN(< long_array_of_items>)`在PostgreSQL中是快的?

t5fffqht  于 2023-11-18  发布在  PostgreSQL
关注(0)|答案(3)|浏览(116)

我发现我自己有时需要根据id或其他变量的列表选择不同表的许多项。考虑到这个列表可以包含数千个元素,对我来说过滤器不会变成n*m查询是很重要的。我经历过在某些情况下postgres可以快速查找,有时不能,例如,如果我通过主键进行过滤,当它进行缓慢过滤时,我通常通过创建IN查询表并将其与表连接以进行过滤来解决它。
但是现在我又回到了这个问题上,我真的很想听听有人能不能帮我建立一些直觉,什么时候我应该假设使用查找,什么时候使用哈希表。
在我目前的工作中,我们使用的是postgres 15,但我的大部分经验都来自postgres 10,所以可能会有一些旧版本的坏习惯。

polkgigr

polkgigr1#

v14引入了一个优化(commit 50 e17 ad 281 b8 d1c 1b)对大型IN列表使用哈希表,而不是对每个候选行在IN列表上进行线性搜索。因此,从那时起,你不应该得到O(nm)的行为。可能会有一些极端情况,就像如果在-列表是动态计算的,或者类型不完全兼容。查询计划中没有任何内容表明正在进行此优化(除了降低的成本估计)。
即使在v14之前,如果被测试的列被索引,那么它可以为列表的每个成员跳一次索引,从而避免O(n
m)的行为。在幕后,这是一个循环,但在查询计划中,它只是作为索引扫描呈现,索引条件为Index Cond: (col1 = ANY('{...

31moq8wy

31moq8wy2#

为什么要假设你可以explain analyze verbose。如果没有看到它取代了什么以及如何取代的例子,很难评论为什么IN * 工作得如此之好 *。
即使你有足够的直觉去相信它,如果你 * 信任,但要验证 *,你仍然会更好。服务器配置,索引设置,表大小,流量以及vacuum analyze d的频率都可以影响规划器的结果,并且它可以改变版本。在这种情况下,所有人类的直觉注定是不可靠的,并且很快就会过时。
也就是说,here are some 1M-row tests at db<>fiddle,这里有一些comparisons of in , any , exists , join behaviour,这里有some more, more visual

hk8txs48

hk8txs483#

以前我在某个时候使用column = ANY (VALUES (...), ...),而不是使用IN进行N次批处理请求,因为它更快(基本上它创建一个临时表,然后与它连接),但看起来在Postgres 15中,这不是一个案例,即使有2000个值,常规的IN也比值列表过滤器快2倍。(但可能在某个N时,它仍然比IN快)
sku_test表设置为

CREATE TABLE sku_test (
    code integer PRIMARY KEY
);

字符串
大约有22 k条记录

EXPLAIN ANALYZE 
SELECT * FROM sku_test
WHERE code IN (64309, 64310, ...);


导致

Seq Scan on sku_test  (cost=5.00..450.03 rows=2000 width=4) (actual time=0.343..13.693 rows=2000 loops=1)
  Filter: (code = ANY ('{64309, 64310, ...}'::integer[]))
  Rows Removed by Filter: 20869
Planning Time: 7.619 ms
Execution Time: 14.275 ms


EXPLAIN ANALYZE 
SELECT * FROM sku_test
WHERE code = ANY(VALUES (64309), (64310), ...);


导致

Hash Semi Join  (cost=50.00..462.97 rows=2000 width=4) (actual time=4.169..28.998 rows=2000 loops=1)
  Hash Cond: (sku_test.code = "*VALUES*".column1)
  ->  Seq Scan on sku_test  (cost=0.00..330.69 rows=22869 width=4) (actual time=0.055..10.293 rows=22869 loops=1)
  ->  Hash  (cost=25.00..25.00 rows=2000 width=4) (actual time=4.033..4.040 rows=2000 loops=1)
        Buckets: 2048  Batches: 1  Memory Usage: 87kB
        ->  Values Scan on "*VALUES*"  (cost=0.00..25.00 rows=2000 width=4) (actual time=0.010..2.344 rows=2000 loops=1)
Planning Time: 6.780 ms
Execution Time: 29.746 ms


我在生产数据库上看到了类似的结果,其中包含活表和过滤列上的索引。
但同样,自己衡量总是更好的。
upd:作为@FrankHeikens回复的示例,这里是一个带有文本列和唯一索引的1 M表的示例,以及~ 10 k过滤器

CREATE TABLE test AS SELECT round(random() * 1e12)::TEXT AS code FROM pg_catalog.generate_series(1, 1e6);
CREATE UNIQUE INDEX itest ON test(code);


IN

EXPLAIN ANALYZE
SELECT code FROM test
WHERE code IN (
'801642034794',
'579842212309',
...
);


导致

Gather  (cost=1024.89..13676.39 rows=9955 width=12) (actual time=81.602..405.323 rows=9955 loops=1)
  Workers Planned: 2
  Workers Launched: 2
  ->  Parallel Seq Scan on test  (cost=24.89..11680.89 rows=4148 width=12) (actual time=5.277..258.142 rows=3318 loops=3)
        Filter: (code = ANY ('{801642034794,579842212309, ...}'::text[]))
        Rows Removed by Filter: 330015
Planning Time: 33.708 ms
Execution Time: 408.542 ms


使用VALUES

Gather  (cost=1248.88..12956.94 rows=9955 width=12) (actual time=147.406..656.538 rows=9955 loops=1)
  Workers Planned: 2
  Workers Launched: 2
  ->  Hash Semi Join  (cost=248.88..10961.44 rows=4148 width=12) (actual time=28.546..450.157 rows=3318 loops=3)
        Hash Cond: (test.code = "*VALUES*".column1)
        ->  Parallel Seq Scan on test  (cost=0.00..9572.67 rows=416667 width=12) (actual time=1.610..164.941 rows=333333 loops=3)
        ->  Hash  (cost=124.44..124.44 rows=9955 width=32) (actual time=24.629..24.658 rows=9955 loops=3)
              Buckets: 16384  Batches: 1  Memory Usage: 565kB
              ->  Values Scan on "*VALUES*"  (cost=0.00..124.44 rows=9955 width=32) (actual time=0.157..12.857 rows=9955 loops=3)
Planning Time: 30.204 ms
Execution Time: 659.705 ms

相关问题