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
?
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:
WHERE (a,b) IN ((1,1),(1,2));
.. 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.