Index wird nicht mit `= any ()` verwendet, sondern mit `in`

15

Tabelle that zwei Indizes:

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

Für den anyOperator wird kein Index verwendet :

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

Aber einer von ihnen wird mit dem inOperator verwendet:

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

Es verwendet den Datensatzindex, wenn der Datensatz in den richtigen Typ umgewandelt wird:

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

Warum verwendet der Planer den Nicht-Datensatz-Index für den anyBediener nicht wie für den inBediener?

Clodoaldo
quelle
Diese interessante Frage ergab
Erwin Brandstetter

Antworten:

13

Intern gibt es zwei getrennte Formen von INsowie für das ANYKonstrukt.

Einer von beiden, der eine Menge nimmt , entspricht dem anderen und expr IN (<set>)führt auch zu demselben Abfrageplan expr = ANY(<set>), der einen einfachen Index verwenden kann. Einzelheiten:

Infolgedessen sind die folgenden beiden Abfragen äquivalent und beide können den einfachen Index verwenden t_a_b_idx(was auch die Lösung sein kann, wenn Sie versuchen, Ihre Abfrage zur Verwendung des Index zu veranlassen):

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

Oder:

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

Identisch für beide:

                                                        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

Dies kann jedoch nicht einfach an eine Funktion übergeben werden, da es in Postgres keine "Tabellenvariablen" gibt. Was zu dem Problem führt, mit dem dieses Thema begonnen hat:

Für dieses Problem gibt es verschiedene Problemumgehungen. Eine davon ist die alternative Antwort, die ich dort hinzugefügt habe. Einige andere:


Die zweite Form von jedem ist anders: ANYnimmt ein tatsächliches Array an , währendIN eine durch Kommas getrennte Liste von Werten verwendet .

Dies hat unterschiedliche Folgen für die Eingabe der Eingabe. Wie wir im sehen könnenEXPLAIN Ausgabe der Frage sehen können, ist dieses Formular:

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

wird als Abkürzung gesehen für:

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

Die tatsächlichen ROW-Werte werden verglichen. Postgres ist derzeit nicht intelligent genug, um den Index für den zusammengesetzten Typ zu erkennent_row_idx anwendbar ist. Es wird auch nicht klar, dass der einfache Index ebenfalls t_a_b_idxanwendbar sein sollte.

Eine explizite Besetzung hilft, diesen Mangel an Intelligenz zu überwinden:

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

Das Umsetzen des richtigen Operanden ( ::int_pair[]) ist optional (jedoch aus Gründen der Leistung und zur Vermeidung von Mehrdeutigkeiten vorzuziehen). Sobald der linke Operand einen bekannten Typ hat, wird der rechte Operand vom "anonymen Datensatz" zu einem passenden Typ gezwungen. Erst dann ist der Operator eindeutig definiert. Und Postgres wählt anwendbare Indizes basierend auf dem Operator und der linken Seite aus Operanden aus. Für viele Operatoren, die a definieren COMMUTATOR, kann der Abfrageplaner Operanden spiegeln, um den indizierten Ausdruck nach links zu verschieben. Mit dem ANYKonstrukt ist das aber nicht möglich .

Verbunden:

.. Werte werden als Elemente genommen und Postgres kann einzelne ganzzahlige Werte vergleichen, wie wir in der EXPLAINAusgabe noch einmal sehen können:

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

Daher findet Postgres den einfachen Index t_a_b_idx verwendet werden kann.


Folglich gibt es im Beispiel eine andere Lösung für den speziellen Fall : Da der benutzerdefinierte zusammengesetzte Typ int_pairim Beispiel dem Zeilentyp der Tabelle tselbst entspricht, können wir Folgendes vereinfachen:

CREATE INDEX t_row_idx2 ON t ((t));

Dann würde diese Abfrage den Index ohne expliziteres Casting verwenden:

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

In typischen Anwendungsfällen kann der implizit vorhandene Typ der Tabellenzeile jedoch nicht verwendet werden.

Erwin Brandstetter
quelle
1
Ein kleiner Zusatz: Während eine kurze IN(...)Liste ... OR ...im obigen Fall (vom Planer) in einen Ausdruck übersetzt werden ANY('{...}')kann, wird sie normalerweise nur in ein Array übersetzt. In den meisten Fällen sind also INeine Werteliste und ANYein Array dasselbe.
Dezso
1
@dezso: Für die meisten einfachen Fälle ja. Die Frage zeigt einen Fall, in den IN(...) nicht übersetzt werden kann = ANY('{...}').
Erwin Brandstetter