Index non utilisé avec `= any ()` mais utilisé avec `in`


15

La table ta deux index:

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

Aucun index n'est utilisé avec l' anyopérateur:

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

Mais l'un d'eux est utilisé avec l' inopérateur:

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

Il utilise l'index d'enregistrement si l'enregistrement est converti dans le type correct:

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

Pourquoi le planificateur n'utilise-t-il pas l'index non enregistré pour l' anyopérateur comme il l'utilise pour l' inopérateur?


Cette question intéressante a émergé d'une discussion connexe sur SO: stackoverflow.com/a/34601242/939860
Erwin Brandstetter

Réponses:


13

En interne, il existe deux formes distinctes de IN, ainsi que pour la ANYconstruction.

L'un de chacun, prenant un ensemble , est équivalent à l'autre et expr IN (<set>)conduit également au même plan de requête que expr = ANY(<set>)celui qui peut utiliser un index simple. Détails:

Par conséquent, les deux requêtes suivantes sont équivalentes et les deux peuvent utiliser l'index brut t_a_b_idx(qui peut également être la solution si vous essayez d'obtenir que votre requête utilise l'index):

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

Ou:

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

Identique pour les deux:

                                                        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

Cependant , cela ne peut pas être facilement transmis à une fonction, car il n'y a pas de "variables de table" dans Postgres. Ce qui conduit au problème à l'origine de ce sujet:

Il existe différentes solutions de contournement pour ce problème. L'une étant la réponse alternative que j'ai ajoutée ici. Quelques autres:


La deuxième forme de chacun est différente: ANYprend un tableau réel , tandis que INprend une liste de valeurs séparées par des virgules .

Cela a des conséquences différentes pour taper l'entrée. Comme nous pouvons le voir dans la EXPLAINsortie de la question, ce formulaire:

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

est considéré comme un raccourci pour:

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

Et les valeurs réelles de ROW sont comparées. Postgres n'est pas actuellement assez intelligent pour voir que l'index sur le type composite t_row_idxest applicable. Elle ne se rend pas non plus compte que l'indice simple t_a_b_idxdevrait également s'appliquer.

Une distribution explicite aide à surmonter ce manque d'intelligence:

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

La conversion de l'opérande droit ( ::int_pair[]) est facultative (bien que préférable pour les performances et pour éviter les ambiguïtés). Une fois que l'opérande gauche a un type bien connu, l'opérande droit est contraint de "l'enregistrement anonyme" à un type correspondant. Alors seulement, l'opérateur est défini sans ambiguïté. Et Postgres sélectionne les index applicables en fonction de l' opérateur et de l' opérande de gauche . Pour de nombreux opérateurs qui définissent un COMMUTATOR, le planificateur de requêtes peut inverser les opérandes pour amener l'expression indexée vers la gauche. Mais ce n'est pas possible avec la ANYconstruction.

En relation:

.. les valeurs sont prises comme éléments et Postgres est capable de comparer les valeurs entières individuelles comme nous pouvons le voir EXPLAINune fois de plus dans la sortie:

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

Postgres trouve donc que l'index simple t_a_b_idxpeut être utilisé.


Par conséquent, il y aurait une autre solution pour le cas particulier dans l'exemple : puisque le type composite personnalisé int_pairdans l'exemple se trouve être équivalent au type de ligne de la table telle - même, nous pourrions simplifier:

CREATE INDEX t_row_idx2 ON t ((t));

Ensuite, cette requête utiliserait l'index sans conversion plus explicite:

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

Mais les cas d'utilisation typiques ne pourront pas utiliser le type implicitement existant de la ligne de table.


1
Un ajout mineur: alors qu'une courte IN(...)liste peut être traduite (par le planificateur) en une ... OR ...expression dans le cas ci-dessus, elle est généralement traduite en ANY('{...}'), c'est-à-dire en utilisant un tableau. Donc, dans la plupart des cas, INavec une liste de valeurs et ANYavec un tableau, c'est la même chose.
dezso

1
@dezso: Pour la plupart des cas simples, oui. La question montre un cas dans lequel IN(...) ne peut pas être traduit = ANY('{...}').
Erwin Brandstetter
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.