Hier sind die Ergebnisse der Abfrage einer Tabelle in der zweiten Spalte eines mehrspaltigen Index .
Die Effekte sind für jedermann leicht zu reproduzieren. Probieren Sie es einfach zu Hause.
Ich habe mit PostgreSQL 9.0.5 unter Debian mit einer mittelgroßen Tabelle einer realen Datenbank mit 23322 Zeilen getestet. Es implementiert die n: m-Beziehung zwischen den Tabellen adr
(Adresse) und att
(Attribut), aber das ist hier nicht relevant. Vereinfachtes Schema:
CREATE TABLE adratt (
adratt_id serial PRIMARY KEY
, adr_id integer NOT NULL
, att_id integer NOT NULL
, log_up timestamp(0) NOT NULL DEFAULT (now())::timestamp(0)
, CONSTRAINT adratt_uni UNIQUE (adr_id, att_id)
);
Die UNIQUE
Einschränkung implementiert effektiv einen eindeutigen Index. Ich wiederholte den Test mit einem einfachen Index, um sicherzugehen, und erhielt die gleichen Ergebnisse wie erwartet.
CREATE INDEX adratt_idx ON adratt(adr_id, att_id)
Die Tabelle ist im adratt_uni
Index zusammengefasst und vor dem Test habe ich Folgendes ausgeführt:
CLUSTER adratt;
ANALYZE adratt;
Sequentielle Abfragen (adr_id, att_id)
werden so schnell wie möglich ausgeführt. Der mehrspaltige Index wird weiterhin nur für eine Abfragebedingung in der zweiten Indexspalte verwendet.
Ich habe die Abfragen ein paar Mal ausgeführt, um den Cache zu füllen, und aus zehn Läufen die besten ausgewählt, um vergleichbare Ergebnisse zu erzielen.
1. Abfrage mit beiden Spalten
SELECT *
FROM adratt
WHERE att_id = 90
AND adr_id = 10;
adratt_id | adr_id | att_id | log_up
-----------+--------+--------+---------------------
123 | 10 | 90 | 2008-07-29 09:35:54
(1 row)
Ausgabe von EXPLAIN ANALYZE
:
Index Scan using adratt_uni on adratt (cost=0.00..3.48 rows=1 width=20) (actual time=0.022..0.025 rows=1 loops=1)
Index Cond: ((adr_id = 10) AND (att_id = 90))
Total runtime: 0.067 ms
2. Abfrage mit der ersten Spalte
SELECT * FROM adratt WHERE adr_id = 10
adratt_id | adr_id | att_id | log_up
-----------+--------+--------+---------------------
126 | 10 | 10 | 2008-07-29 09:35:54
125 | 10 | 13 | 2008-07-29 09:35:54
4711 | 10 | 21 | 2008-07-29 09:35:54
29322 | 10 | 22 | 2011-06-06 15:50:38
29321 | 10 | 30 | 2011-06-06 15:47:17
124 | 10 | 62 | 2008-07-29 09:35:54
21913 | 10 | 78 | 2008-07-29 09:35:54
123 | 10 | 90 | 2008-07-29 09:35:54
28352 | 10 | 106 | 2010-11-22 12:37:50
(9 rows)
Ausgabe von EXPLAIN ANALYZE
:
Index Scan using adratt_uni on adratt (cost=0.00..8.23 rows=9 width=20) (actual time=0.007..0.023 rows=9 loops=1)
Index Cond: (adr_id = 10)
Total runtime: 0.058 ms
3. Abfrage über zweite Spalte
SELECT * FROM adratt WHERE att_id = 90
adratt_id | adr_id | att_id | log_up
-----------+--------+--------+---------------------
123 | 10 | 90 | 2008-07-29 09:35:54
180 | 39 | 90 | 2008-08-29 15:46:07
...
(83 rows)
Ausgabe von EXPLAIN ANALYZE
:
Index Scan using adratt_uni on adratt (cost=0.00..818.51 rows=83 width=20) (actual time=0.014..0.694 rows=83 loops=1)
Index Cond: (att_id = 90)
Total runtime: 0.849 ms
4. Deaktivieren Sie Indexscan und Bitmapscan
SET enable_indexscan = off;
SELECT * FROM adratt WHERE att_id = 90
Ausgabe von EXPLAIN ANALYZE:
Bitmap Heap Scan on adratt (cost=779.94..854.74 rows=83 width=20) (actual time=0.558..0.743 rows=83 loops=1)
Recheck Cond: (att_id = 90)
-> Bitmap Index Scan on adratt_uni (cost=0.00..779.86 rows=83 width=0) (actual time=0.544..0.544 rows=83 loops=1)
Index Cond: (att_id = 90)
Total runtime: 0.894 ms
SET enable_bitmapscan = off
SELECT * FROM adratt WHERE att_id = 90
Ausgabe von EXPLAIN ANALYZE
:
Seq Scan on adratt (cost=0.00..1323.10 rows=83 width=20) (actual time=0.009..2.429 rows=83 loops=1)
Filter: (att_id = 90)
Total runtime: 2.680 ms
Fazit
Wie erwartet wird der mehrspaltige Index nur für eine Abfrage in der zweiten Spalte verwendet.
Wie erwartet ist es weniger effektiv, aber die Abfrage ist immer noch dreimal schneller als ohne den Index.
Nach dem Deaktivieren von Index-Scans wählt der Abfrageplaner einen Bitmap-Heap-Scan, der fast genauso schnell ausgeführt wird. Erst wenn auch dies deaktiviert ist, wird auf einen sequentiellen Scan zurückgegriffen.
vacuum full
und a istreindex
. Ansonsten hilft es, die Suchläufe in der ersten oder beiden führenden Spalten stark zu indizieren , aber die Suchläufe in der zweiten Spalte zu beeinträchtigen. In einer frisch gruppierten Tabelle werden Zeilen mit demselben Wert in der zweiten Spalte so verteilt, dass maximal Blöcke gelesen werden müssen.zu 1) Ja und nein.
Bei einer Abfrage, die beide Spalten verwendet, spielt es zB
where (user_id1, user_id2) = (1,2)
keine Rolle, welcher Index erstellt wird.Für eine Abfrage, die nur eine Bedingung für eine der Spalten enthält
where user_id1 = 1
, ist dies beispielsweise von Bedeutung, da der Optimierer normalerweise nur die "führenden" Spalten für einen Vergleich verwenden kann. Somitwhere user_id1 = 1
könnte der Index (user_id1, user_id2) verwendet werden, aber nicht für alle Fälle ein Index (user_id2, user_id1).Nachdem ich damit herumgespielt habe (nachdem Erwin uns so freundlich ein Setup gezeigt hat, wo es funktioniert), scheint es, dass dies stark von der Datenverteilung der zweiten Spalte abhängt, obwohl ich noch nicht herausgefunden habe, in welcher Situation der Optimierer nachfolgende Spalten verwenden kann für eine WHERE-Bedingung.
Oracle 11 kann (manchmal) auch Spalten verwenden, die nicht am Anfang der Indexdefinition stehen.
Zu 2) Ja, es wird ein Index erstellt
Zitat aus dem Handbuch
Zu 2a)
Primary Key (user_id1,user_id2)
wird ein Index für (user_id1, user_id2) erstellt (was Sie sehr einfach selbst herausfinden können, indem Sie einfach einen solchen Primärschlüssel erstellen).Ich empfehle Ihnen nachdrücklich, das Kapitel über Indizes im Handbuch zu lesen. Es beantwortet im Grunde alle oben gestellten Fragen.
Außerdem, welcher Index erstellt werden soll? von depesz erklärt die Reihenfolge von Indexspalten und anderen indexbezogenen Themen sehr gut.
quelle
Ad 1)
Es gibt Einschränkungen in PostgreSQL wie @a_horse_with_no_name beschreibt . Bis zur Version 8.0 konnten mehrspaltige Indizes nur für Abfragen in den führenden Spalten verwendet werden. Dies wurde in Version 8.1 verbessert. Das aktuelle Handbuch für Postgres 10 (aktualisiert) erklärt:
Betonung meiner. Das kann ich aus Erfahrung bestätigen.
Siehe auch den Testfall, der meiner späteren Antwort hier hinzugefügt wurde .
quelle
Dies ist eine Antwort auf Jacks Antwort , ein Kommentar würde nicht genügen.
Vor Version 9.2 gab es in PostgreSQL keine Deckungsindizes . Aufgrund des MVCC-Modells muss jedes Tupel in der Ergebnismenge besucht werden, um die Sichtbarkeit zu überprüfen. Möglicherweise denken Sie an Oracle.
PostgreSQL-Entwickler sprechen von "Nur-Index-Scans" . Tatsächlich wurde die Funktion mit Postgres 9.2 veröffentlicht. Lesen Sie die Commit-Nachricht .
Depesz hat einen sehr informativen Blog-Beitrag geschrieben .
True-Covering-Indizes (Update) werden mit der
INCLUDE
Klausel mit Postgres 11 eingeführt.Das ist auch ein bisschen abwegig:
Wie in den Kommentaren zu meiner anderen Antwort angegeben, habe ich auch Tests mit einer Tabelle mit zwei ganzen Zahlen und sonst nichts durchgeführt. Der Index enthält dieselben Spalten wie die Tabelle. Die Größe eines Btree-Index beträgt ungefähr 2/3 der Größe der Tabelle. Nicht genug, um eine Beschleunigung von Faktor 3 zu erklären. Ich habe mehr Tests durchgeführt, basierend auf Ihrem Setup, vereinfacht auf zwei Spalten und mit 100000 Zeilen. Bei meiner PostgreSQL 9.0-Installation waren die Ergebnisse konsistent.
Wenn die Tabelle zusätzliche Spalten enthält, wird die Beschleunigung mit dem Index beträchtlicher, aber dies ist sicherlich nicht der einzige Faktor .
Um die wichtigsten Punkte zusammenzufassen:
Mehrspaltenindizes können mit Abfragen für nicht führende Spalten verwendet werden, aber die Beschleunigung liegt nur bei Faktor 3 für ausgewählte Kriterien (kleiner Prozentsatz der Zeilen im Ergebnis). Höher für größere Tupel, niedriger für größere Teile der Tabelle in der Ergebnismenge.
Erstellen Sie einen zusätzlichen Index für diese Spalten, wenn die Leistung wichtig ist.
Wenn alle beteiligten Spalten in einem Index enthalten sind (der den Index abdeckt) und alle beteiligten Zeilen (pro Block) für alle Transaktionen sichtbar sind, können Sie in S. 9.2 oder höher einen "Nur-Index-Scan" durchführen .
quelle
Diese sind nicht gleichwertig und im Allgemeinen ist der Index (Balken, Baz) für Abfragen des Formulars nicht effizient
select * from foo where baz=?
Erwin hat gezeigt, dass solche Indizes zwar eine Abfrage beschleunigen können, aber dieser Effekt ist begrenzt und nicht in der gleichen Größenordnung, wie Sie allgemein erwarten, dass ein Index eine Suche verbessert. Er beruht auf der Tatsache, dass ein Index häufig vollständig durchsucht wird Schneller als ein vollständiger Scan der indizierten Tabelle aufgrund der zusätzlichen Spalten in der Tabelle, die nicht im Index enthalten sind.
Zusammenfassung: Indizes können bei Abfragen auch für nicht führende Spalten hilfreich sein, jedoch auf eine von zwei sekundären und relativ geringfügigen Arten und nicht auf die dramatische Weise, die ein Index normalerweise aufgrund seiner Btree-Struktur bietet
nb die zwei Möglichkeiten, die der Index bietet, sind, wenn ein vollständiger Index-Scan erheblich günstiger ist als ein vollständiger Tabellenscan und entweder: 1. die Tabellensuche ist günstig (weil es nur wenige gibt oder sie in Clustern vorliegen),
oder 2. Der Index deckt, also gibt es überhaupt keine Tabellensuche, siehe Erwins Kommentare hierPrüfstand:
Abfrage 1 (kein Index, Treffer 74 Puffer ):
Abfrage 2 (mit Index - der Optimierer ignoriert den Index - Treffer) 74 Puffer ):
Abfrage 2 (mit Index - und wir täuschen den Optimierer vor, ihn zu verwenden):
Der Zugriff über den Index ist in diesem Fall also doppelt so schnell wie bei 30 Puffern - was in Bezug auf die Indizierung "etwas schneller" ist! der Daten in der Tabelle
Im Gegensatz dazu verwenden Abfragen in der führenden Spalte die Btree-Struktur des Index - in diesem Fall 2 Puffer :
quelle