PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Hinweise zu PostgreSQL B-Tree-Indizes

PostgreSQL verfügt über nicht weniger als 6 verschiedene Arten von Indizes, wobei der B-Treeindex am häufigsten verwendet wird. Lesen Sie weiter, um mehr über B-Tree-Indizes in PostgreSQL zu erfahren.

Arten von Indizes

Ein Index in PostgreSQL, wie diejenigen, die für PRIMARY KEYs und UNIQUEs in einer CREATE TABLE-Anweisung oder explizit mit einer CREATE INDEX-Anweisung erstellt wurden, sind von einem bestimmten „Typ“ (obwohl wir sie technisch gesehen „Indexzugriffsmethoden“ nennen sollten).

PostgreSQL enthält diese integrierten Indextypen:

  • B-Baum
  • Hash
  • GIN – Verallgemeinerter invertierter Index
  • BRIN – Block Range Index (nur in v9.5 und höher)
  • GiST – Verallgemeinerter invertierter Suchbaum
  • SP-GiST – Speicherplatzpartitioniertes GiST

B-Tree ist der Standard und der am häufigsten verwendete Indextyp. Die Angabe eines Primärschlüssels oder eines eindeutigen Schlüssels innerhalb einer CREATE TABLE-Anweisung veranlasst PostgreSQL, B-Tree-Indizes zu erstellen. CREATE INDEX-Anweisungen ohne die USING-Klausel erstellen auch B-Tree-Indizes:

-- the default index type is btree
CREATE INDEX ix_year ON movies (year);

-- equivalent, explicitly lists the index type
CREATE INDEX ix_year ON movies USING btree (year);

Bestellung

B-Tree-Indizes sind von Natur aus geordnet. PostgreSQL kann diese Reihenfolge verwenden, anstatt nach dem indizierten Ausdruck zu sortieren. Um beispielsweise die Titel aller 80er-Filme nach Titel sortiert zu erhalten, wäre eine Sortierung erforderlich:

idxdemo=# explain select title from movies where year between 1980 and 1989 order by title asc;
                                    QUERY PLAN
----------------------------------------------------------------------------------
 Sort  (cost=240.79..245.93 rows=2056 width=17)
   Sort Key: title
   ->  Index Scan using ix_year on movies  (cost=0.29..127.65 rows=2056 width=17)
         Index Cond: ((year >= 1980) AND (year <= 1989))
(4 rows)

Wenn Sie sie jedoch nach der indizierten Spalte (Jahr) sortieren, ist eine zusätzliche Sortierung nicht erforderlich.

idxdemo=# explain select title from movies where year between 1980 and 1989 order by year asc;
                                 QUERY PLAN
----------------------------------------------------------------------------
 Index Scan using ix_year on movies  (cost=0.29..127.65 rows=2056 width=21)
   Index Cond: ((year >= 1980) AND (year <= 1989))
(2 rows)

Füllfaktor

Für Tabellen, die nicht aktualisiert werden, können Sie den „Füllfaktor“ vom Standardwert 90 erhöhen, wodurch Sie etwas kleinere und schnellere Indizes erhalten sollten. Umgekehrt können Sie bei häufigen Aktualisierungen der Tabelle mit dem indizierten Parameter den Füllfaktor auf eine kleinere Zahl reduzieren – dies ermöglicht schnellere Einfügungen und Aktualisierungen auf Kosten etwas größerer Indizes.

CREATE INDEX ix_smd ON silent_movies (director) WITH (fillfactor = 100);

Indizierung auf Text

B-Tree-Indizes können beim Präfixabgleich von Text helfen. Nehmen wir eine Abfrage, um alle Filme aufzulisten, die mit dem Buchstaben „T“ beginnen:

idxdemo=> explain select title from movies where title like 'T%';
                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on movies  (cost=0.00..1106.94 rows=8405 width=17)
   Filter: (title ~~ 'T%'::text)
(2 rows)

Dieser Plan erfordert einen vollständigen sequentiellen Scan der Tabelle. Was passiert, wenn wir movie.title einen B-Tree-Index hinzufügen?

idxdemo=> create index ix_title on movies (title);
CREATE INDEX

idxdemo=> explain select title from movies where title like 'T%';
                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on movies  (cost=0.00..1106.94 rows=8405 width=17)
   Filter: (title ~~ 'T%'::text)
(2 rows)

Nun, das hat überhaupt nicht geholfen. Es gibt jedoch eine Art magischen Feenstaub, den wir verstreuen können, damit Postgres tut, was wir wollen:

idxdemo=> create index ix_title2 on movies (title text_pattern_ops);
CREATE INDEX

idxdemo=> explain select title from movies where title like 'T%';
                                 QUERY PLAN
-----------------------------------------------------------------------------
 Bitmap Heap Scan on movies  (cost=236.08..1085.19 rows=8405 width=17)
   Filter: (title ~~ 'T%'::text)
   ->  Bitmap Index Scan on ix_title2  (cost=0.00..233.98 rows=8169 width=0)
         Index Cond: ((title ~>=~ 'T'::text) AND (title ~<~ 'U'::text))
(4 rows)

Der Plan verwendet jetzt einen Index, und die Kosten sind gesunken. Die Magie hier ist „text_pattern_ops“, die es ermöglicht, den B-Tree-Index über einen „Text“-Ausdruck für Musteroperatoren (LIKE und reguläre Ausdrücke) zu verwenden. Das „text_pattern_ops“ wird OperatorClass genannt.

Beachten Sie, dass dies nur für Muster mit einem festen Textpräfix funktioniert, also funktionieren „%Angry%“ oder „%Men“ nicht. Verwenden Sie die Volltextsuche von PostgreSQL für erweiterte Textabfragen.

Abdeckende Indizes

Überdeckende Indizes wurden in v11 zu PostgreSQL hinzugefügt. Durch das Abdecken von Indizes können Sie den Wert eines oder mehrerer Ausdrücke zusammen mit dem indizierten Ausdruck in den Index aufnehmen.

Versuchen wir, nach allen Filmtiteln zu suchen, sortiert nach Erscheinungsjahr:

idxdemo=# explain select title from movies order by year asc;
                             QUERY PLAN
--------------------------------------------------------------------
 Sort  (cost=3167.73..3239.72 rows=28795 width=21)
   Sort Key: year
   ->  Seq Scan on movies  (cost=0.00..1034.95 rows=28795 width=21)
(3 rows)

Dies beinhaltet einen vollständigen sequentiellen Scan der Tabelle, gefolgt von einer Sortierung der projizierten Spalten. Lassen Sie uns zunächst einen regulären Index für movies.year hinzufügen:

idxdemo=# create index ix_year on movies (year);
CREATE INDEX

idxdemo=# explain select title from movies order by year asc;
                                  QUERY PLAN
------------------------------------------------------------------------------
 Index Scan using ix_year on movies  (cost=0.29..1510.22 rows=28795 width=21)
(1 row)

Nun entscheidet sich Postgres dafür, den Index zu nutzen, um die Einträge direkt in der gewünschten Reihenfolge aus der Tabelle zu ziehen. Die Tabelle muss nachgeschlagen werden, da der Index nur den Wert von „Jahr“ und die Referenz auf das Tupel in der Tabelle enthält.

Wenn wir den Wert von „Titel“ auch in den Index aufnehmen, kann die Tabellensuche vollständig vermieden werden. Lassen Sie uns die neue Syntax verwenden, um einen solchen Index zu erstellen:

idxdemo=# create index ix_year_cov on movies (year) include (title);
CREATE INDEX
Time: 92.618 ms

idxdemo=# drop index ix_year;
DROP INDEX

idxdemo=# explain select title from movies order by year asc;
                                      QUERY PLAN
---------------------------------------------------------------------------------------
 Index Only Scan using ix_year_cov on movies  (cost=0.29..2751.59 rows=28795 width=21)
(1 row)

Postgres verwendet jetzt einen Index OnlyScan, was bedeutet, dass die Tabellensuche vollständig vermieden wird. Beachten Sie, dass wir den alten Index löschen mussten, weil Postgres für diese Abfrage nicht ix_year_cov statt ix_year gewählt hat.

Clustering

PostgreSQL unterstützt bekanntermaßen keine automatische physische Anordnung von Zeilen in einer Tabelle, im Gegensatz zu „Clustered-Indizes“ in anderen RDBMS. Wenn die meisten Ihrer Abfragen die meisten Zeilen einer meist statischen Tabelle in einer festen Reihenfolge abrufen, wäre es eine gute Idee, den physischen Tabellenspeicher in dieser Reihenfolge zu gestalten und sequentielle Scans zu verwenden. Um eine Tabelle physisch in der durch einen Index vorgegebenen Reihenfolge neu anzuordnen, verwenden Sie:

CLUSTER VERBOSE movies USING ix_year;

Sie würden normalerweise einen B-Tree-Index verwenden, um eine Tabelle neu zu gruppieren, da er eine vollständige Reihenfolge für alle Zeilen in der Tabelle bereitstellt.

Indexstatistiken

Wie viel Speicherplatz nimmt Ihr Index ein? Die Funktion pg_relation_size kann das beantworten:

idxdemo=# select * from pg_relation_size('ix_year');
 pg_relation_size
------------------
           663552
(1 row)

Dies gibt den vom Index belegten Speicherplatz in Byte zurück.

Weitere Informationen zum Index können mit der Standarderweiterung pgstattuple abgerufen werden. Bevor Sie die nachstehenden Funktionen verwenden, müssen Sie ein CREATE EXTENSION pgstattuple; ausführen in der entsprechenden Datenbank als Superuser. Die Nutzung dieser Funktionen erfordert auch Superuser-Privilegien.

Das pgstattuple Funktion gibt unter anderem den ungenutzten (free_space ) und wiederverwendbar (dead_tuple_len ) Speicherplatz im Index. Dies kann bei der Entscheidung, ob ein REINDEX ausgeführt werden soll, sehr hilfreich sein um das Aufblähen des Index zu reduzieren.

idxdemo=# select * from pgstattuple('ix_year'::regclass);
-[ RECORD 1 ]------+-------
table_len          | 663552
tuple_count        | 28795
tuple_len          | 460720
tuple_percent      | 69.43
dead_tuple_count   | 0
dead_tuple_len     | 0
dead_tuple_percent | 0
free_space         | 66232
free_percent       | 9.98

Das pgstattuple Die Funktion gibt B-Baum-spezifische Informationen zurück, einschließlich der Ebene des Baums:

idxdemo=# select * from pgstatindex('ix_year'::regclass);
-[ RECORD 1 ]------+-------
version            | 2
tree_level         | 1
index_size         | 663552
root_block_no      | 3
internal_pages     | 1
leaf_pages         | 79
empty_pages        | 0
deleted_pages      | 0
avg_leaf_density   | 89.72
leaf_fragmentation | 0

Damit kann entschieden werden, ob der Füllfaktor des Index angepasst werden soll.

Prüfung des Inhalts des B-Tree-Index

Auch der Inhalt des B-Trees kann mit der Extensionpageinspect direkt untersucht werden. Die Verwendung dieser Erweiterung erfordert Superuser-Rechte.

Hier sind die Eigenschaften einer einzelnen Seite (hier die 13. Seite) des Indexes:

idxdemo=# select * from bt_page_stats('ix_year', 13);
-[ RECORD 1 ]-+-----
blkno         | 13
type          | l
live_items    | 367
dead_items    | 0
avg_item_size | 16
page_size     | 8192
free_size     | 808
btpo_prev     | 12
btpo_next     | 14
btpo          | 0
btpo_flags    | 1

Und hier sind die tatsächlichen Inhalte jedes Elements (hier auf 5 begrenzt) auf der Seite:

idxdemo=# select * from bt_page_items('ix_year', 13) limit 5;
 itemoffset |   ctid   | itemlen | nulls | vars |          data
------------+----------+---------+-------+------+-------------------------
          1 | (104,40) |      16 | f     | f    | 86 07 00 00 00 00 00 00
          2 | (95,38)  |      16 | f     | f    | 86 07 00 00 00 00 00 00
          3 | (95,39)  |      16 | f     | f    | 86 07 00 00 00 00 00 00
          4 | (95,40)  |      16 | f     | f    | 86 07 00 00 00 00 00 00
          5 | (96,1)   |      16 | f     | f    | 86 07 00 00 00 00 00 00
(5 rows)

Und wenn Sie daran denken, eine Abfrage zu schreiben, um etwas über jede Seite zu aggregieren, benötigen Sie auch die Gesamtzahl der Seiten in der Relation, die über pg_relpages abgerufen werden kann aus dem pgstattuple Erweiterung:

idxdemo=# select pg_relpages('ix_year');
 pg_relpages
-------------
          81
(1 row)

Andere Indextypen

B-Tree-Indizes sind vielseitige Tools zur Optimierung von Abfragen. Mit ein wenig Experimentieren und Planung kann es verwendet werden, um die Antwortzeiten von Anwendungen und Berichtsaufträgen erheblich zu verbessern.

Die anderen Indextypen von PostgreSQL sind ebenfalls nützlich und können in bestimmten Fällen effizienter und performanter sein als B-Tree. Dieser Artikel gibt einen schnellen Überblick über alle Typen.

Haben Sie einen Tipp zu Indizes, den Sie teilen möchten? Hinterlasse sie unten als Kommentar!