A tabela t
possui dois í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)
;
Nenhum índice é usado com o ANY
operador:
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
Mas um deles é usado com o IN
operador:
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
Ele usa o índice de registro se o registro for convertido para o tipo correto:
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 que o planejador não usa o índice de não registro para o ANY
operador como o usa para o IN
operador?
Internamente, existem duas formas separadas de
IN
, e também duas formas separadas daANY
construção.Um de cada, tomando um set , é equivalente ao outro e
expr IN (<set>)
também leva ao mesmo plano de consultaexpr = ANY(<set>)
que pode usar um índice simples. Detalhes:Consequentemente, as duas consultas a seguir são equivalentes e ambas podem usar o índice simples
t_a_b_idx
(que também pode ser a solução se você estiver tentando fazer com que sua consulta use o índice):Ou:
Idêntico para ambos:
No entanto , isso não pode ser passado facilmente para uma função, pois não há "variáveis de tabela" no Postgres. O que leva ao problema que iniciou este tópico:
Existem várias soluções alternativas para esse problema. Uma delas é a resposta alternativa que adicionei lá. Alguns outros:
A segunda forma de cada um é diferente:
ANY
leva uma matriz real , enquantoIN
leva uma lista separada por vírgulas de valores .Isso tem consequências diferentes para digitar a entrada. Como podemos ver na
EXPLAIN
saída da pergunta, este formulário:é visto como uma abreviação para:
E os valores ROW reais são comparados. Postgres atualmente não é inteligente o suficiente para ver que o índice no tipo composto
t_row_idx
é aplicável. Também não percebe que o índice simplest_a_b_idx
também deve ser aplicável.Um elenco explícito ajuda a superar essa falta de inteligência:
A conversão do operando correto (
::int_pair[]
) é opcional (embora seja preferível para desempenho e para evitar ambiguidades). Uma vez que o operando esquerdo tenha um tipo conhecido, o operando direito é forçado de "registro anônimo" para um tipo correspondente. Só então, o operador é definido de forma inequívoca. E o Postgres escolhe os índices aplicáveis com base no operador e no operando esquerdo . Para muitos operadores que definem umCOMMUTATOR
, o planejador de consulta pode inverter os operandos para trazer a expressão indexada para a esquerda. Mas isso não é possível com aANY
construção.Relacionado:
Existe uma maneira útil de indexar uma coluna de texto contendo padrões regex?
.. os valores são tomados como elementos e o Postgres é capaz de comparar valores inteiros individuais, como podemos ver na
EXPLAIN
saída mais uma vez:Portanto, o Postgres descobre que o índice simples
t_a_b_idx
pode ser usado.Consequentemente, haveria outra solução para o caso particular do exemplo : como o tipo composto personalizado
int_pair
no exemplo é equivalente ao tipo de linha dat
própria tabela, poderíamos simplificar:Sintaxe equivalente mais curta:
Mas a primeira variante é mais segura. A segunda variante resolveria para a coluna se uma coluna com o mesmo nome existisse.
Então esta consulta usaria o índice sem qualquer conversão mais explícita:
Mas os casos de uso típicos não poderão utilizar o tipo implicitamente existente da linha da tabela.