Índice no usado con `= any ()` pero usado con `in`

15

La tabla ttiene dos índices:

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

No se utiliza índice con el anyoperador:

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

Pero uno de ellos se usa con el inoperador:

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

Utiliza el índice de registro si el registro se convierte al tipo correcto:

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

¿Por qué el planificador no usa el índice sin registro para el anyoperador como lo usa para el inoperador?

Clodoaldo
fuente
Esta interesante pregunta surgió de una discusión relacionada sobre SO: stackoverflow.com/a/34601242/939860
Erwin Brandstetter

Respuestas:

13

Internamente, hay dos formas separadas de IN, así como para la ANYconstrucción.

Uno de cada uno, tomando un conjunto , es equivalente al otro y expr IN (<set>)también conduce al mismo plan de consulta expr = ANY(<set>)que puede usar un índice simple. Detalles:

En consecuencia, las siguientes dos consultas son equivalentes y ambas pueden usar el índice simple t_a_b_idx(que también puede ser la solución si está intentando que su consulta use el índice):

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

O:

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

Idéntico para ambos:

                                                        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

Sin embargo , esto no se puede pasar fácilmente a una función, ya que no hay "variables de tabla" en Postgres. Lo que lleva al problema que inició este tema:

Hay varias soluciones para ese problema. Una es la respuesta alternativa que agregué allí. Algunos otros:


La segunda forma de cada uno es diferente: ANYtoma una matriz real , mientras que INtoma una lista de valores separados por comas .

Esto tiene diferentes consecuencias para escribir la entrada. Como podemos ver en el EXPLAINresultado de la pregunta, este formulario:

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

es visto como una forma abreviada de:

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

Y se comparan los valores reales de ROW. Postgres no es lo suficientemente inteligente como para ver que el índice en el tipo compuesto t_row_idxes aplicable. Tampoco se da cuenta de que el índice simple también t_a_b_idxdebería ser aplicable.

Un reparto explícito ayuda a superar esta falta de inteligencia:

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

Lanzar el operando correcto ( ::int_pair[]) es opcional (aunque preferible para el rendimiento y para evitar ambigüedades). Una vez que el operando izquierdo tiene un tipo bien conocido, el operando derecho se convierte de "registro anónimo" a un tipo coincidente. Solo entonces, el operador se define inequívocamente. Y Postgres elige índices aplicables basados ​​en el operador y el operando izquierdo . Para muchos operadores que definen a COMMUTATOR, el planificador de consultas puede voltear operandos para llevar la expresión indexada a la izquierda. Pero eso no es posible con la ANYconstrucción.

Relacionado:

.. los valores se toman como elementos y Postgres puede comparar valores enteros individuales como podemos ver en la EXPLAINsalida una vez más:

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

Por lo tanto, Postgres encuentra que t_a_b_idxse puede usar el índice simple .


En consecuencia, habría otra solución para el caso particular en el ejemplo : dado que el tipo compuesto personalizado int_pairen el ejemplo es equivalente al tipo de fila de la tabla en tsí, podríamos simplificar:

CREATE INDEX t_row_idx2 ON t ((t));

Luego, esta consulta usaría el índice sin más conversión explícita:

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

Pero los casos de uso típicos no podrán utilizar el tipo implícitamente existente de la fila de la tabla.

Erwin Brandstetter
fuente
1
Una adición menor: mientras que una IN(...)lista corta puede ser traducida (por el planificador) a una ... OR ...expresión en el caso anterior, generalmente solo se traduce ANY('{...}'), es decir, usando una matriz. Entonces, en la mayoría de los casos, INcon una lista de valores y ANYcon una matriz son lo mismo.
dezso
1
@dezso: para la mayoría de los casos simples, sí. La pregunta demuestra un caso en el IN(...) que no se puede traducir = ANY('{...}').
Erwin Brandstetter