it-swarm.com.de

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

Tabelle t hat 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)
;

Mit dem Operator any 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 Operator in 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 wurde:

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 nicht für den Operator any, wie er ihn für den Operator in verwendet?

16
Clodoaldo

Intern gibt es zwei separate Formen von IN sowie für das Konstrukt ANY.

Einer von jedem, der ein set nimmt, entspricht dem anderen und expr IN (<set>) führt auch zu demselben Abfrageplan wie expr = ANY(<set>), die einen einfachen Index verwenden können. Einzelheiten:

Folglich sind die folgenden zwei Abfragen äquivalent und beide können den einfachen Index t_a_b_idx Verwenden (der auch die Lösung sein kann, wenn Sie versuchen, Ihre Abfrage dazu zu bringen, den Index zu verwenden):

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

Jedoch, dies kann 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:

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


Die zweite Form von jedem ist unterschiedlich: ANY nimmt ein tatsächliches Array, während IN ein durch Kommas getrenntes nimmt Liste der Werte.

Dies hat unterschiedliche Konsequenzen für Eingabe die Eingabe. Wie wir in der Ausgabe EXPLAIN der Frage sehen können, ist dieses Formular:

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

wird als Abkürzung für:

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

Und die tatsächlichen ROW-Werte werden verglichen. Postgres ist derzeit nicht intelligent genug, um zu erkennen, dass der Index für den zusammengesetzten Typ t_row_idx Anwendbar ist. Es ist auch nicht klar, dass der einfache Index t_a_b_idx Auch anwendbar 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 Umwandeln 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. Nur dann ist der Operator eindeutig definiert. Und Postgres wählt anwendbare Indizes basierend auf dem Operanden - Operator und dem Operanden left aus. Für viele Operatoren, die ein COMMUTATOR definieren, kann der Abfrageplaner Operanden spiegeln, um den indizierten Ausdruck nach links zu bringen. Mit dem Konstrukt ANY ist dies jedoch nicht möglich.

Verbunden:

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

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

Daher stellt Postgres fest, dass der einfache Index t_a_b_idx Verwendet werden kann.


Folglich würde es eine andere Lösung für den speziellen Fall im Beispiel geben: da der benutzerdefinierte zusammengesetzte Typ int_pair Im Beispiel zufällig dem Zeilentyp der Tabelle t selbst könnten wir 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

Typische Anwendungsfälle können jedoch den implizit vorhandenen Typ der Tabellenzeile nicht verwenden.

14