web-dev-qa-db-fra.com

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

La table t a 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'opérateur 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

Mais l'un d'eux est utilisé avec l'opérateur 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

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 d'enregistrement pour l'opérateur any comme il l'utilise pour l'opérateur in?

16
Clodoaldo

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

L'un de chacun, prenant un set ​​, est équivalent à l'autre et expr IN (<set>) conduit également à la même plan de requête que expr = ANY(<set>) 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 simple 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 facilement être passé à 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: ANY prend un réel array, tandis que IN prend une virgule séparée liste de valeurs.

Cela a des conséquences différentes pour la saisie de l'entrée. Comme nous pouvons le voir dans la sortie EXPLAIN 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_idx est applicable. Il ne se rend pas non plus compte que l'index simple t_a_b_idx devrait é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[]);

Casting de l'opérande droit (::int_pair[]) est facultatif (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érande opérateur et 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 construction ANY.

En relation:

.. les valeurs sont prises comme elements et Postgres est capable de comparer les valeurs entières individuelles comme nous pouvons le voir dans la sortie EXPLAIN une fois de plus:

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

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


Par conséquent, il y aurait une autre solution pour le cas particulier dans l'exemple : puisque le type composite personnalisé int_pair dans l'exemple se trouve être équivalent au type de ligne de la table t lui-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.

14
Erwin Brandstetter