web-dev-qa-db-ja.com

Postgresに部分インデックスと大きなIn-clauseがある場合の実行計画が悪い

Postgresは常に部分スキャンを使用してインデックススキャンのみを取得できた場合、シーケンシャルスキャンを使用するようです。これは、節内の要素が100を超える場合にのみ発生します。

次の表があるとします。

create table foo(id bigint primary key, bar bigint); 

insert into foo (id, bar) 
select g.id, case when id % 1000 = 0 then id else null end
from generate_series(1, 10000000) AS g (id) ;

--Create partial index
create unique index ix_foo_bar on foo(bar) where bar is not null;

analyze foo;

また、次のクエリに大きなステートメントが含まれています。

explain analyze select count(*) from foo where bar in (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101);

クエリプランは順次スキャンを示しています。それは遅く、コストがかかります:

QUERY PLAN                                                                             
------------------------------------
 Finalize Aggregate  (cost=612955.35..612955.36 rows=1 width=8) (actual time=254.605..254.605 rows=1 loops=1)
   ->  Gather  (cost=612955.13..612955.34 rows=2 width=8) (actual time=254.474..258.242 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Partial Aggregate  (cost=611955.13..611955.14 rows=1 width=8) (actual time=247.743..247.744 rows=1 loops=3)
               ->  Parallel Seq Scan on foo  (cost=0.00..611955.03 rows=42 width=0) (actual time=247.740..247.740 rows=0 loops=3)
                     Filter: (bar = ANY ('{1,2,3,4,5(...)
                     Rows Removed by Filter: 3333333
 Planning Time: 0.867 ms
 Execution Time: 258.323 ms

set enable_seqscanは効果がなく、順次スキャンを実行します。

クエリに「not null」を追加すると、インデックスが使用されます。

explain analyze select count(*) from foo where bar is not null and bar in (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101);

インデックスのみのスキャン:

    QUERY PLAN                                                                                                                                                                        
----------------------------------------------------------------------
 Aggregate  (cost=153.55..153.56 rows=1 width=8) (actual time=0.267..0.267 rows=1 loops=1)
   ->  Index Only Scan using ix_foo_bar on foo  (cost=0.29..153.55 rows=1 width=0) (actual time=0.262..0.262 rows=0 loops=1)
         Index Cond: (bar = ANY ('{1,2,3,4,5,6 (...), 101}'::bigint[]))
         Heap Fetches: 0
 Planning Time: 0.531 ms
 Execution Time: 0.319 ms
(6 rows)

また、節内の要素が少ない場合(カットオフが100対101の場合)、または部分的なインデックスではなく完全なインデックスがある場合にも、インデックスを使用します。

要素数が100を超えるin-clauseがある場合、なぜPostgresは部分インデックスを使用しないのですか?これはクエリプランナーの既知の制限ですか、それともバグですか?

これは バージョン12で修正されます で、まもなくリリースされます。

ここでの要約は、部分索引を使用できることを証明するために多くの作業を行うだけでよいということだと思います。部分索引を使用しなくても、すべてのクエリがその作業を通過する必要があるためです。この変更では、この特定のケースでそれを行うためのより効率的な方法を見つけたので、100要素の制限を課すことはなくなりました。

3
jjanes