インデックスは `= any()`では使用されず、 `in`で使用されます


15

テーブルにtは2つのインデックスがあります。

create table t (a int, b int);
create type int_pair as (a int, b int);
create index t_row_idx on t (((a,b)::int_pair));
create index t_a_b_idx on t (a,b);

insert into t (a,b)
select i, i
from generate_series(1, 100000) g(i)
;

any演算子ではインデックスは使用されません。

explain analyze
select *
from t
where (a,b) = any(array[(1,1),(1,2)])
;
                                            QUERY PLAN                                             
---------------------------------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..1693.00 rows=1000 width=8) (actual time=0.042..126.789 rows=1 loops=1)
   Filter: (ROW(a, b) = ANY (ARRAY[ROW(1, 1), ROW(1, 2)]))
   Rows Removed by Filter: 99999
 Planning time: 0.122 ms
 Execution time: 126.836 ms

ただし、そのうちの1つはin演算子で使用されます。

explain analyze
select *
from t
where (a,b) in ((1,1),(1,2))
;
                                                    QUERY PLAN                                                    
------------------------------------------------------------------------------------------------------------------
 Index Only Scan using t_a_b_idx on t  (cost=0.29..8.32 rows=1 width=8) (actual time=0.028..0.029 rows=1 loops=1)
   Index Cond: (a = 1)
   Filter: ((b = 1) OR (b = 2))
   Heap Fetches: 1
 Planning time: 0.161 ms
 Execution time: 0.066 ms

レコードが正しいタイプにキャストされる場合、レコードインデックスを使用します。

explain analyze
select *
from t
where (a,b)::int_pair = any(array[row(1,1),row(1,2)])
;
                                                  QUERY PLAN                                                  
--------------------------------------------------------------------------------------------------------------
 Index Scan using t_row_idx on t  (cost=0.42..12.87 rows=2 width=8) (actual time=0.106..0.126 rows=1 loops=1)
   Index Cond: (ROW(a, b)::int_pair = ANY (ARRAY[ROW(1, 1), ROW(1, 2)]))
 Planning time: 0.208 ms
 Execution time: 0.203 ms

なぜプランナーはany演算子に使用するので、演算子に非レコードインデックスを使用しないのinですか?


:これは興味深い質問は、SOに関連した議論から出てstackoverflow.com/a/34601242/939860
アーウィンBrandstetter

回答:


13

内部的に、コンストラクトと同様に、の2つの独立した形式があります。INANY

セットをとるそれぞれの1つは、もう1 つと同等であり、プレーンインデックスを使用できるのexpr IN (<set>)と同じクエリプランになりexpr = ANY(<set>)ます。詳細:

したがって、次の2つのクエリは同等であり、どちらもプレーンインデックスを使用できますt_a_b_idx(インデックスを使用するクエリを取得しようとしている場合、これも解決策になります)。

EXPLAIN ANALYZE
SELECT *
FROM t
WHERE (a,b) = ANY(VALUES (1,1),(1,2));

または:

...
WHERE (a,b) IN (VALUES (1,1),(1,2));

両方に同じ:

                                                        QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.33..16.71 rows=1 width=8) (actual time=0.101..0.101 rows=0 loops=1)
   ->  Unique  (cost=0.04..0.05 rows=2 width=8) (actual time=0.068..0.070 rows=2 loops=1)
         ->  Sort  (cost=0.04..0.04 rows=2 width=8) (actual time=0.067..0.068 rows=2 loops=1)
               Sort Key: "*VALUES*".column1, "*VALUES*".column2
               Sort Method: quicksort  Memory: 25kB
               ->  Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=8) (actual time=0.005..0.005 rows=2 loops=1)
   ->  Index Only Scan using t_plain_idx on t  (cost=0.29..8.32 rows=1 width=8) (actual time=0.009..0.009 rows=0 loops=2)
         Index Cond: ((a = "*VALUES*".column1) AND (b = "*VALUES*".column2))
         Heap Fetches: 0
 Planning time: 4.080 ms
 Execution time: 0.202 ms

ただし、Postgresには「テーブル変数」がないため、これを関数に簡単に渡すことはできません。これは、このトピックを開始した問題につながります。

その問題にはさまざまな回避策があります。1つは、私がそこに追加した代替の回答です。他の何人か:


それぞれの2番目の形式は異なりANYます。実際の配列IN受け取り、値のコンマ区切りリストを受け取ります

これはのためのさまざまな影響があるタイピング入力を。EXPLAIN質問の出力でわかるように、この形式は次のとおりです。

WHERE (a,b) = ANY(ARRAY[(1,1),(1,2)]);

以下の略記と見なされます:

ROW(a, b) = ANY (ARRAY[ROW(1, 1), ROW(1, 2)])

そして、実際のROW値が比較されます。現時点では、Postgresは、複合型のインデックスt_row_idxが適用可能であることを確認できるほどスマートではありません。また、単純なインデックスt_a_b_idxも同様に適用可能でなければならないことを認識していません。

明示的なキャストは、このスマートの欠如を克服するのに役立ちます。

WHERE (a,b)::int_pair = ANY(ARRAY[(1,1),(1,2)]::int_pair[]);

右側のオペランド(::int_pair[])のキャストはオプションです(ただし、パフォーマンスとあいまいさを避けるために望ましい)。左のオペランドが既知のタイプになると、右のオペランドは「匿名レコード」から一致するタイプに強制されます。その場合にのみ、演算子が明確に定義されます。そして、Postgresは演算子左に基づいて適切なインデックスを選択しますオペランドを。を定義する多くの演算子のCOMMUTATOR場合、クエリプランナーはオペランドを反転して、インデックス付きの式を左に移動できます。しかし、それはANYコンストラクトでは不可能です。

関連:

..値は要素として扱われ、PostgresはEXPLAIN出力で確認できるように個々の整数値を比較できます。

Filter: ((b = 1) OR (b = 2))

したがって、Postgresは単純なインデックス t_a_b_idxを使用できることを。


したがって、例の特定のケースには別の解決策があります。のカスタム複合型int_pairは、たまたまテーブルt自体の行型と同等であるため、単純化できます。

CREATE INDEX t_row_idx2 ON t ((t));

次に、このクエリは、明示的なキャストなしでインデックスを使用します。

EXPLAIN ANALYZE
SELECT *
FROM   t
WHERE  t = ANY(ARRAY[(1,1),(1,2)]);
                                                      QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on t  (cost=40.59..496.08 rows=1000 width=8) (actual time=0.19
1..0.191 rows=0 loops=1)
   Recheck Cond: (t.* = ANY (ARRAY[ROW(1, 1), ROW(1, 2)]))
   ->  Bitmap Index Scan on t_row_idx2  (cost=0.00..40.34 rows=1000 width=0) (actual time=0.188..0.188 rows=0 loops=1)
         Index Cond: (t.* = ANY (ARRAY[ROW(1, 1), ROW(1, 2)]))
 Planning time: 2.575 ms
 Execution time: 0.267 ms

しかし、典型的なユースケースでは、暗黙的に存在するタイプのテーブル行を利用することはできません。


1
ちょっとした追加:上記の場合、短いIN(...)リストは(プランナーによって)... OR ...式に変換されますがANY('{...}')、通常は単に、つまり配列を使用して変換されます。そのため、ほとんどの場合、IN値のリストとANY配列は同じものです。
-dezso

1
@dezso:ほとんどの単純な場合、はい。質問は、 に翻訳IN(...) できない場合を示してます= ANY('{...}')
アーウィンブランドステッター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.