Indice non usato con `= any ()` ma usato con `in`


15

La tabella tha due indici:

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

Nessun indice viene utilizzato con l' anyoperatore:

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

Ma uno di questi viene utilizzato con l' inoperatore:

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

Utilizza l'indice del record se il record viene trasmesso al tipo corretto:

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

Perché il pianificatore non utilizza l'indice non record per l' anyoperatore come lo usa per l' inoperatore?


Questa interessante domanda è emersa da una relativa discussione su SO: stackoverflow.com/a/34601242/939860
Erwin Brandstetter,

Risposte:


13

Internamente, ci sono due forme separate di IN, oltre che per il ANYcostrutto.

Uno di questi, prendendo un set , è equivalente all'altro e expr IN (<set>)porta anche allo stesso piano di query expr = ANY(<set>)che può utilizzare un indice semplice. Dettagli:

Di conseguenza, le seguenti due query sono equivalenti ed entrambe possono utilizzare l'indice semplice t_a_b_idx(che può anche essere la soluzione se si sta tentando di ottenere la query per utilizzare l'indice):

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

O:

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

Identico per entrambi:

                                                        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

Tuttavia , questo non può essere facilmente trasferito a una funzione, poiché in Postgres non ci sono "variabili di tabella". Il che porta al problema che ha iniziato questo argomento:

Esistono varie soluzioni alternative per questo problema. Una è la risposta alternativa che ho aggiunto lì. Alcuni altri:


La seconda forma di ciascuna è diversa: ANYaccetta un array reale , mentre INprende una virgola separata elenco di valori.

Ciò ha conseguenze diverse per la digitazione dell'input. Come possiamo vedere EXPLAINnell'output della domanda, questo modulo:

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

è visto come una scorciatoia per:

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

E vengono confrontati i valori ROW effettivi. Postgres non è attualmente abbastanza intelligente da vedere che l'indice sul tipo composito t_row_idxè applicabile. Né si rende conto che t_a_b_idxdovrebbe essere applicabile anche l'indice semplice .

Un cast esplicito aiuta a superare questa mancanza di intelligenza:

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

Il cast dell'operando giusto ( ::int_pair[]) è facoltativo (sebbene preferibile per le prestazioni e per evitare ambiguità). Una volta che l'operando di sinistra ha un tipo noto, l'operando di destra viene forzato da "record anonimo" a un tipo corrispondente. Solo allora l'operatore viene definito in modo inequivocabile. E Postgres seleziona gli indici applicabili in base all'operatore e all'operando di sinistra . Per molti operatori che definiscono aCOMMUTATOR , il planner di query può capovolgere gli operandi per portare l'espressione indicizzata a sinistra. Ma questo non è possibile con il ANYcostrutto.

Relazionato:

.. i valori sono presi come elementi e Postgres è in grado di confrontare i singoli valori interi come possiamo vedere EXPLAINancora una volta nell'output:

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

Quindi Postgres scopre che è t_a_b_idxpossibile utilizzare l' indice semplice .


Di conseguenza, ci sarebbe un'altra soluzione per il caso particolare nell'esempio : poiché il tipo composito personalizzato int_pairnell'esempio sembra essere equivalente al tipo di riga della tabella tstessa, potremmo semplificare:

CREATE INDEX t_row_idx2 ON t ((t));

Quindi questa query userebbe l'indice senza altri casting espliciti:

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

Ma i casi d'uso tipici non saranno in grado di utilizzare il tipo implicitamente esistente della riga della tabella.


1
Un'aggiunta minore: mentre un breve IN(...)elenco può essere tradotto (dal pianificatore) in ... OR ...un'espressione nel caso precedente, di solito viene semplicemente tradotto in ANY('{...}'), cioè usando un array. Quindi, nella maggior parte dei casi, INcon un elenco di valori e ANYcon un array sono la stessa cosa.
dezso

1
@dezso: per la maggior parte dei casi semplici, sì. La domanda dimostra un caso in cui IN(...) non è possibile tradurlo = ANY('{...}').
Erwin Brandstetter,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.