Indeks tidak digunakan dengan `= any ()` tetapi digunakan dengan `in`

15

Tabel tmemiliki dua indeks:

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)
;

Tidak ada indeks yang digunakan dengan anyoperator:

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

Tetapi salah satunya digunakan dengan inoperator:

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

Ini menggunakan indeks catatan jika catatan dilemparkan ke jenis yang benar:

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

Mengapa perencana tidak menggunakan indeks non-rekaman untuk anyoperator seperti yang digunakan untuk inoperator?

Clodoaldo
sumber
Pertanyaan menarik ini muncul dari diskusi terkait pada SO: stackoverflow.com/a/34601242/939860
Erwin Brandstetter

Jawaban:

13

Secara internal, ada dua bentuk terpisah dari IN, serta untuk ANYmembangun.

Salah satu dari masing-masing, mengambil satu set , setara dengan yang lain dan expr IN (<set>)juga mengarah ke rencana kueri yang sama seperti expr = ANY(<set>)yang dapat menggunakan indeks biasa. Detail:

Akibatnya, dua kueri berikut ini sama dan keduanya dapat menggunakan indeks polos t_a_b_idx(yang juga bisa menjadi solusi jika Anda mencoba untuk mendapatkan kueri Anda untuk menggunakan indeks):

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

Atau:

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

Identik untuk keduanya:

                                                        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

Namun , ini tidak dapat dengan mudah diteruskan ke suatu fungsi, karena tidak ada "variabel tabel" di Postgres. Yang mengarah ke masalah yang memulai topik ini:

Ada berbagai solusi untuk masalah itu. Salah satu jawaban alternatif yang saya tambahkan di sana. Beberapa lainnya:


Bentuk kedua dari masing-masing berbeda: ANYmengambil array aktual , sementara INmengambil daftar nilai yang dipisahkan koma .

Ini memiliki konsekuensi berbeda untuk mengetik input. Seperti yang dapat kita lihat di EXPLAINoutput pertanyaan, formulir ini:

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

dipandang sebagai singkatan untuk:

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

Dan nilai ROW aktual dibandingkan. Postgres saat ini tidak cukup pintar untuk melihat bahwa indeks pada tipe komposit t_row_idxberlaku. Juga tidak menyadari bahwa indeks sederhana juga t_a_b_idxharus berlaku.

Para pemeran yang eksplisit membantu mengatasi kekurangan kecerdasan ini:

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

Casting operan yang tepat ( ::int_pair[]) adalah opsional (meskipun lebih disukai untuk kinerja dan untuk menghindari ambiguitas). Setelah operan kiri memiliki jenis terkenal, operan kanan dipaksa dari "catatan anonim" ke jenis yang cocok. Hanya kemudian, operator didefinisikan dengan jelas. Dan Postgres mengambil indeks yang berlaku berdasarkan pada operator dan operan kiri . Untuk banyak operator yang mendefinisikan COMMUTATOR, perencana kueri dapat membalik operan untuk membawa ekspresi yang diindeks ke kiri. Tapi itu tidak mungkin dengan ANYkonstruknya.

Terkait:

.. nilai diambil sebagai elemen dan Postgres dapat membandingkan nilai integer individual seperti yang dapat kita lihat dalam EXPLAINoutput sekali lagi:

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

Karenanya Postgres menemukan bahwa indeksnya sederhana t_a_b_idx dapat digunakan.


Akibatnya, akan ada solusi lain untuk kasus tertentu dalam contoh : karena tipe komposit kustom int_pairdalam contoh tersebut setara dengan jenis baris tabel titu sendiri, kami dapat menyederhanakan:

CREATE INDEX t_row_idx2 ON t ((t));

Maka kueri ini akan menggunakan indeks tanpa casting yang lebih eksplisit:

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

Tetapi kasus penggunaan umum tidak akan dapat menggunakan jenis baris tabel yang ada secara implisit.

Erwin Brandstetter
sumber
1
Tambahan kecil: sementara IN(...)daftar pendek dapat diterjemahkan (oleh perencana) ke dalam ... OR ...ekspresi dalam kasus di atas, biasanya hanya diterjemahkan ke dalam ANY('{...}'), yaitu menggunakan array. Jadi, dalam banyak kasus, INdengan daftar nilai dan ANYdengan array adalah hal yang sama.
dezso
1
@dezso: Untuk kebanyakan kasus sederhana, ya. Pertanyaan itu menunjukkan kasus di mana IN(...) tidak dapat diterjemahkan = ANY('{...}').
Erwin Brandstetter