it-swarm.com.de

Arbeiten von Indizes in PostgreSQL

Ich habe einige Fragen zum Arbeiten mit Indizes in PostgreSQL. Ich habe eine Friends Tabelle mit dem folgenden Index:

   Friends ( user_id1 ,user_id2) 

user_id1 und user_id2 sind Fremdschlüssel für die Tabelle user

  1. Sind diese gleichwertig? Wenn nicht, warum dann?

    Index(user_id1,user_id2) and Index(user_id2,user_id1)
    
  2. Wenn ich einen Primärschlüssel (Benutzer-ID1, Benutzer-ID2) erstelle, werden automatisch Indizes für diesen und erstellt

    Wenn die Indizes in der ersten Frage nicht gleichwertig sind, welcher Index wird dann mit dem obigen Primärschlüsselbefehl erstellt?

80
codecool

Hier sind die Ergebnisse der Abfrage einer Tabelle in zweite Spalte eines mehrspaltigen Index.
Die Effekte sind für jeden leicht zu reproduzieren. Probieren Sie es einfach zu Hause aus.

Ich habe mit PostgreSQL 9.0.5 auf Debian unter Verwendung 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 Einschränkung UNIQUE implementiert effektiv einen eindeutigen Index. Ich wiederholte den Test mit einem einfachen Index, um sicherzugehen, und erhielt identische Ergebnisse wie erwartet.

CREATE INDEX adratt_idx ON adratt(adr_id, att_id)

Die Tabelle ist auf dem adratt_uni index und vor dem test habe ich ausgeführt:

CLUSTER adratt;
ANALYZE adratt;

Sequentielle Suche nach Abfragen für (adr_id, att_id) sind so schnell wie möglich. 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 das Beste 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 mit der zweiten 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 x 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 nachdem dies ebenfalls deaktiviert wurde, wird auf einen sequentiellen Scan zurückgegriffen.

83

zu 1) Ja und Nein.

Für eine Abfrage, die beide Spalten verwendet, z. where (user_id1, user_id2) = (1,2) es spielt keine Rolle, welcher Index erstellt wird.

Für eine Abfrage, die nur eine Bedingung für eine der Spalten hat, z. where user_id1 = 1 Ist wichtig, da normalerweise nur die "führenden" Spalten für einen Vergleich durch den Optimierer verwendet werden können. where user_id1 = 1 Könnte also den Index (user_id1, user_id2) verwenden, aber nicht für alle Fälle einen Index (user_id2, user_id1).

Nachdem ich damit herumgespielt habe (nachdem Erwin uns freundlicherweise ein Setup gezeigt hat, wo es funktioniert), scheint dies stark von der Datenverteilung der zweiten Spalte abzuhängen, obwohl ich noch nicht herausgefunden habe, in welcher Situation der Optimierer nachfolgende Spalten verwenden kann für eine WHERE-Bedingung.

Oracle 11, das (manchmal) auch Spalten verwenden kann, die nicht am Anfang der Indexdefinition stehen.

zu 2) Ja, es wird ein Index erstellt

Zitat aus dem Handbuch

Durch Hinzufügen eines Primärschlüssels wird automatisch ein eindeutiger btree-Index für die im Primärschlüssel verwendete Spalte oder Spaltengruppe erstellt.

zu 2a) Primary Key (user_id1,user_id2) erstellt einen Index für (user_id1, user_id2) (den Sie selbst sehr einfach herausfinden können einen solchen Primärschlüssel erstellen)

Ich empfehle Ihnen dringend, das Kapitel über Indizes im Handbuch zu lesen. Es beantwortet im Grunde alle oben genannten Fragen.

Außerdem erklärt Welcher Index soll erstellt werden? von depesz die Reihenfolge der Indexspalten und anderer indexbezogener Themen.

30

Anzeige 1)
Es gibt Einschränkungen in PostgreSQL wie @a_horse_with_no_name beschreibt . Bis Version 8. konnten mehrspaltige Indizes nur für Abfragen in den führenden Spalten verwendet werden. Dies wurde in Version 8.1 verbessert. Das aktuelles Handbuch für Postgres 1 (aktualisiert) erklärt:

Ein mehrspaltiger B-Baum-Index kann mit Abfragebedingungen verwendet werden, die eine beliebige Teilmenge der Indexspalten betreffen. Der Index ist jedoch am effizientesten, wenn Einschränkungen für die führenden Spalten (ganz links) bestehen. Die genaue Regel lautet, dass Gleichheitsbeschränkungen für führende Spalten sowie Ungleichheitsbeschränkungen für die erste Spalte ohne Gleichheitsbeschränkung verwendet werden, um den Teil des gescannten Index zu begrenzen. Einschränkungen für Spalten rechts von diesen Spalten werden im Index aktiviert, sodass sie Besuche in der eigentlichen Tabelle speichern, aber nicht den Teil des Index reduzieren, der gescannt werden muss. Beispiel: Ein Index für (a, b, c) und eine Abfragebedingung WHERE a = 5 AND b >= 42 AND c < 77 müsste der Index vom ersten Eintrag mit a = 5 und b = 42 bis zum letzten Eintrag mit a = 5 gescannt werden. Indexeinträge mit c> = 77 würde übersprungen, aber sie müssten noch gescannt werden. Dieser Index könnte im Prinzip für Abfragen verwendet werden, die Einschränkungen für b und/oder c ohne Einschränkung für a haben - aber der gesamte Index müsste gescannt werden In den meisten Fällen würde der Planer einen sequentiellen Tabellenscan der Verwendung des Index vorziehen.

Hervorhebung von mir. Das kann ich aus Erfahrung bestätigen.
Siehe auch den hinzugefügten Testfall meine spätere Antwort hier .

12

Dies ist eine Antwort auf Jacks Antwort , ein Kommentar würde nicht reichen.

Es gab keine abdeckenden Indizes in PostgreSQL vor Version 9.2. Aufgrund des MVCC-Modells muss jedes Tupel in der Ergebnismenge besucht werden, um die Sichtbarkeit zu überprüfen. Sie denken vielleicht 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 schrieb einen sehr informativer Blog-Beitrag .

True Covering Indizes (Update) werden mit der INCLUDE -Klausel mit Postgres 11 eingeführt.

Das ist auch ein bisschen anders:

dies beruht auf der Tatsache, dass ein vollständiger Scan eines Index aufgrund der zusätzlichen Spalten in der Tabelle, die nicht im Index enthalten sind, häufig schneller ist als ein vollständiger Scan der indizierten Tabelle.

Wie in Kommentaren zu meiner anderen Antwort berichtet, 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 hat zusätzliche Spalten, die Beschleunigung mit Index wird substanzieller, aber das ist hier sicherlich nicht der einzige Faktor.

Um die wichtigsten Punkte zusammenzufassen:

  • Mehrspaltige Indizes können mit Abfragen für nicht führende Spalten verwendet werden, aber die Beschleunigung liegt bei ausgewählten Kriterien nur bei Faktor 3 (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 (Index abdecken) 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" erhalten.

12
  1. Sind diese gleichwertig? Wenn nicht, warum dann?

    Index (Benutzer-ID1, Benutzer-ID2) und Index (Benutzer-ID2, Benutzer-ID1)

Diese sind nicht äquivalent und im Allgemeinen ist der Index (bar, baz) für Abfragen der Form select * from foo where baz=? Nicht effizient.

Erwin hat gezeigt , dass solche Indizes tatsächlich eine Abfrage beschleunigen können, aber dieser Effekt ist begrenzt und nicht in der Reihenfolge, in der Sie allgemein erwarten, dass ein Index eine Suche verbessert - er beruht auf der Tatsache, dass ein 'full' Das Scannen eines Index ist häufig schneller als das vollständige Scannen der indizierten Tabelle, da zusätzliche Spalten in der Tabelle nicht im Index enthalten sind.

Zusammenfassung: Indizes können Abfragen auch in nicht führenden Spalten unterstützen, jedoch auf eine von zwei sekundären und relativ kleinen Arten und nicht auf die dramatische Weise, die Sie normalerweise erwarten, dass ein Index aufgrund seiner Baumstruktur hilft

nb Der Index kann auf zwei Arten helfen, wenn ein vollständiger Scan des Index erheblich billiger ist als ein vollständiger Scan der Tabelle und entweder: 1. die Tabellensuche ist billig (weil es nur wenige davon gibt oder sie gruppiert sind), oder 2. der Index ist abdeckend , so dass es überhaupt keine Tabellensuche gibt Hoppla, siehe Erwins Kommentare hier

prüfstand:

create table foo(bar integer not null, baz integer not null, qux text not null);

insert into foo(bar, baz, qux)
select random()*100, random()*100, 'some random text '||g from generate_series(1,10000) g;

abfrage 1 (kein Index, Treffer 74 Puffer ):

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=181.41..181.42 rows=1 width=32) (actual time=3.301..3.302 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..181.30 rows=43 width=32) (actual time=0.043..3.228 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.335 ms

abfrage 2 (mit Index - der Optimierer ignoriert den Index - schlägt erneut 74 Puffer ):

create index bar_baz on foo(bar, baz);

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=199.12..199.13 rows=1 width=32) (actual time=3.277..3.277 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..199.00 rows=50 width=32) (actual time=0.043..3.210 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.311 ms

abfrage 2 (mit Index - und wir betrügen den Optimierer, um ihn zu verwenden):

explain (buffers, analyze, verbose) select max(qux) from foo where bar>-1000 and baz=0;
                                                       QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=115.56..115.57 rows=1 width=32) (actual time=1.495..1.495 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=36 read=30
   ->  Bitmap Heap Scan on stack.foo  (cost=73.59..115.52 rows=17 width=32) (actual time=1.370..1.428 rows=52 loops=1)
         Output: bar, baz, qux
         Recheck Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
         Buffers: shared hit=36 read=30
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..73.58 rows=17 width=0) (actual time=1.356..1.356 rows=52 loops=1)
               Index Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
               Buffers: shared read=30
 Total runtime: 1.535 ms

Der Zugriff über den Index ist also in diesem Fall doppelt so schnell und erreicht 30 Puffer - was in Bezug auf die Indizierung "etwas schneller" ist!, Und YMMV abhängig von die relative Größe von Tabelle und Index sowie die Anzahl der gefilterten Zeilen und die Clustering-Eigenschaften der Daten in der Tabelle

Im Gegensatz dazu verwenden Abfragen in der führenden Spalte die btree-Struktur des Index - in diesem Fall treffen 2 Puffer :

explain (buffers, analyze, verbose) select max(qux) from foo where bar=0;
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=75.70..75.71 rows=1 width=32) (actual time=0.172..0.173 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=38
   ->  Bitmap Heap Scan on stack.foo  (cost=4.64..75.57 rows=50 width=32) (actual time=0.036..0.097 rows=59 loops=1)
         Output: bar, baz, qux
         Recheck Cond: (foo.bar = 0)
         Buffers: shared hit=38
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..4.63 rows=50 width=0) (actual time=0.024..0.024 rows=59 loops=1)
               Index Cond: (foo.bar = 0)
               Buffers: shared hit=2
 Total runtime: 0.209 ms