Verwenden aller Funktionen von Indizes in PostgreSQL


In der Postgres-Welt sind Indizes entscheidend für die effiziente Navigation in einem Datenbank-Repository (Heap, Heap genannt). Postgres unterstützt kein Clustering und die MVCC-Architektur führt dazu, dass Sie viele Versionen desselben Tupels ansammeln. Daher ist es sehr wichtig, effektive Indizes zur Unterstützung von Anwendungen erstellen und verwalten zu können.

Hier finden Sie einige Tipps zur Optimierung und Verbesserung der Verwendung von Indizes.

Hinweis: Die unten gezeigten Abfragen funktionieren in einer unveränderten Pagila-Beispieldatenbank .

Abdecken von Indizes


Lassen Sie uns die Anforderung zum Abrufen von E-Mail-Adressen für inaktive Benutzer überprüfen. Die customer hat eine active Spalte und die Anfrage ist einfach:

 pagila=# EXPLAIN SELECT email FROM customer WHERE active=0; QUERY PLAN ----------------------------------------------------------- Seq Scan on customer (cost=0.00..16.49 rows=15 width=32) Filter: (active = 0) (2 rows) 

Die Abfrage ruft die vollständige Sequenz des Scannens der customer . Erstellen wir einen Index für die active Spalte:

 pagila=# CREATE INDEX idx_cust1 ON customer(active); CREATE INDEX pagila=# EXPLAIN SELECT email FROM customer WHERE active=0; QUERY PLAN ----------------------------------------------------------------------------- Index Scan using idx_cust1 on customer (cost=0.28..12.29 rows=15 width=32) Index Cond: (active = 0) (2 rows) 

Es hat geholfen, der nachfolgende Scan wurde zu einem " index scan ". Dies bedeutet, dass Postgres den Index idx_cust1 und dann den Heap der Tabelle weiter durchsucht, um die Werte der anderen Spalten (in diesem Fall der email Spalte) zu lesen, die die Abfrage benötigt.

PostgreSQL 11 führte Indexe ein. Mit ihnen können Sie eine oder mehrere zusätzliche Spalten in den Index selbst aufnehmen - ihre Werte werden im Indexdatenspeicher gespeichert.

Wenn wir diese Funktion verwenden und einen E-Mail-Wert in den Index einfügen würden, müsste Postgres den email Wert im Tabellenheap nicht nachschlagen. Mal sehen, ob das funktioniert:

 pagila=# CREATE INDEX idx_cust2 ON customer(active) INCLUDE (email); CREATE INDEX pagila=# EXPLAIN SELECT email FROM customer WHERE active=0; QUERY PLAN ---------------------------------------------------------------------------------- Index Only Scan using idx_cust2 on customer (cost=0.28..12.29 rows=15 width=32) Index Cond: (active = 0) (2 rows) 

" Index Only Scan " gibt an, dass die Abfrage nur noch einen Index benötigt, wodurch alle Festplatten-E / A zum Lesen des Tabellenheaps vermieden werden.

Abdeckungsindizes sind heute nur für B-Bäume verfügbar. In diesem Fall sind die Begleitbemühungen jedoch höher.

Teilindizes verwenden


Teilindizes indizieren nur eine Teilmenge der Zeilen in einer Tabelle. Dies spart die Größe von Indizes und schnellere Scans.

Angenommen, wir benötigen eine Liste mit E-Mail-Adressen von unseren Kunden in Kalifornien. Die Anfrage lautet wie folgt:

 SELECT c.email FROM customer c JOIN address a ON c.address_id = a.address_id WHERE a.district = 'California'; which has a query plan that involves scanning both the tables that are joined: pagila=# EXPLAIN SELECT c.email FROM customer c pagila-# JOIN address a ON c.address_id = a.address_id pagila-# WHERE a.district = 'California'; QUERY PLAN ---------------------------------------------------------------------- Hash Join (cost=15.65..32.22 rows=9 width=32) Hash Cond: (c.address_id = a.address_id) -> Seq Scan on customer c (cost=0.00..14.99 rows=599 width=34) -> Hash (cost=15.54..15.54 rows=9 width=4) -> Seq Scan on address a (cost=0.00..15.54 rows=9 width=4) Filter: (district = 'California'::text) (6 rows) 

Welche gewöhnlichen Indizes geben uns:

 pagila=# CREATE INDEX idx_address1 ON address(district); CREATE INDEX pagila=# EXPLAIN SELECT c.email FROM customer c pagila-# JOIN address a ON c.address_id = a.address_id pagila-# WHERE a.district = 'California'; QUERY PLAN --------------------------------------------------------------------------------------- Hash Join (cost=12.98..29.55 rows=9 width=32) Hash Cond: (c.address_id = a.address_id) -> Seq Scan on customer c (cost=0.00..14.99 rows=599 width=34) -> Hash (cost=12.87..12.87 rows=9 width=4) -> Bitmap Heap Scan on address a (cost=4.34..12.87 rows=9 width=4) Recheck Cond: (district = 'California'::text) -> Bitmap Index Scan on idx_address1 (cost=0.00..4.34 rows=9 width=0) Index Cond: (district = 'California'::text) (8 rows) 

Der address wurde durch den idx_address1 , und dann wurde der idx_address1 gescannt.

Da dies eine häufige Abfrage ist und optimiert werden muss, können wir einen Teilindex verwenden, der nur die Zeilen mit Adressen indiziert, in denen sich die 'California' :

 pagila=# CREATE INDEX idx_address2 ON address(address_id) WHERE district='California'; CREATE INDEX pagila=# EXPLAIN SELECT c.email FROM customer c pagila-# JOIN address a ON c.address_id = a.address_id pagila-# WHERE a.district = 'California'; QUERY PLAN ------------------------------------------------------------------------------------------------ Hash Join (cost=12.38..28.96 rows=9 width=32) Hash Cond: (c.address_id = a.address_id) -> Seq Scan on customer c (cost=0.00..14.99 rows=599 width=34) -> Hash (cost=12.27..12.27 rows=9 width=4) -> Index Only Scan using idx_address2 on address a (cost=0.14..12.27 rows=9 width=4) (5 rows) 

Jetzt liest die Anfrage nur idx_address2 und berührt die idx_address2 nicht.

Verwenden mehrwertiger Indizes


Einige Spalten, die indiziert werden müssen, enthalten möglicherweise keinen skalaren Datentyp. jsonb wie jsonb , arrays und tsvector enthalten mehrere oder mehrere Werte. Wenn Sie solche Spalten indizieren müssen, müssen Sie normalerweise nach allen einzelnen Werten in diesen Spalten suchen.

Versuchen wir, die Namen aller Filme zu finden, die Schnitte aus erfolglosen Einstellungen enthalten. Die special_features enthält eine special_features Namen special_features . Wenn der Film diese „besondere Eigenschaft“ hat, enthält die Spalte ein Element in Form eines Textarrays Behind The Scenes . Um nach all diesen Filmen zu suchen, müssen wir alle Zeilen mit "Behind The Scenes" für alle Werte des Arrays special_features :

 SELECT title FROM film WHERE special_features @> '{"Behind The Scenes"}'; 

Der Containment-Operator @> prüft, ob die rechte Seite eine Teilmenge der linken Seite ist.

Plan anfordern:

 pagila=# EXPLAIN SELECT title FROM film pagila-# WHERE special_features @> '{"Behind The Scenes"}'; QUERY PLAN ----------------------------------------------------------------- Seq Scan on film (cost=0.00..67.50 rows=5 width=15) Filter: (special_features @> '{"Behind The Scenes"}'::text[]) (2 rows) 

Welches fordert einen vollständigen Heap-Scan mit einem Preis von 67.

Mal sehen, ob der reguläre B-Tree-Index uns hilft:

 pagila=# CREATE INDEX idx_film1 ON film(special_features); CREATE INDEX pagila=# EXPLAIN SELECT title FROM film pagila-# WHERE special_features @> '{"Behind The Scenes"}'; QUERY PLAN ----------------------------------------------------------------- Seq Scan on film (cost=0.00..67.50 rows=5 width=15) Filter: (special_features @> '{"Behind The Scenes"}'::text[]) (2 rows) 

Der Index wurde nicht einmal berücksichtigt. Dem B-Tree-Index ist das Vorhandensein einzelner Elemente in den indizierten Werten nicht bekannt.

Wir brauchen einen GIN-Index.

 pagila=# CREATE INDEX idx_film2 ON film USING GIN(special_features); CREATE INDEX pagila=# EXPLAIN SELECT title FROM film pagila-# WHERE special_features @> '{"Behind The Scenes"}'; QUERY PLAN --------------------------------------------------------------------------- Bitmap Heap Scan on film (cost=8.04..23.58 rows=5 width=15) Recheck Cond: (special_features @> '{"Behind The Scenes"}'::text[]) -> Bitmap Index Scan on idx_film2 (cost=0.00..8.04 rows=5 width=0) Index Cond: (special_features @> '{"Behind The Scenes"}'::text[]) (4 rows) 

Der GIN-Index unterstützt den Vergleich einzelner Werte mit indizierten zusammengesetzten Werten, wodurch sich die Kosten des Abfrageplans um mehr als die Hälfte reduzieren.

Entfernen Sie doppelte Indizes


Indizes häufen sich im Laufe der Zeit an, und manchmal enthält ein neuer Index dieselbe Definition wie einer der vorherigen. Um von Menschen lesbare SQL-Definitionen von Indizes zu erhalten, können Sie die Katalogansicht pg_indexes . Sie können auch leicht die gleichen Definitionen finden:

  SELECT array_agg(indexname) AS indexes, replace(indexdef, indexname, '') AS defn FROM pg_indexes GROUP BY defn HAVING count(*) > 1; And here's the result when run on the stock pagila database: pagila=# SELECT array_agg(indexname) AS indexes, replace(indexdef, indexname, '') AS defn pagila-# FROM pg_indexes pagila-# GROUP BY defn pagila-# HAVING count(*) > 1; indexes | defn ------------------------------------------------------------------------+------------------------------------------------------------------ {payment_p2017_01_customer_id_idx,idx_fk_payment_p2017_01_customer_id} | CREATE INDEX ON public.payment_p2017_01 USING btree (customer_id {payment_p2017_02_customer_id_idx,idx_fk_payment_p2017_02_customer_id} | CREATE INDEX ON public.payment_p2017_02 USING btree (customer_id {payment_p2017_03_customer_id_idx,idx_fk_payment_p2017_03_customer_id} | CREATE INDEX ON public.payment_p2017_03 USING btree (customer_id {idx_fk_payment_p2017_04_customer_id,payment_p2017_04_customer_id_idx} | CREATE INDEX ON public.payment_p2017_04 USING btree (customer_id {payment_p2017_05_customer_id_idx,idx_fk_payment_p2017_05_customer_id} | CREATE INDEX ON public.payment_p2017_05 USING btree (customer_id {idx_fk_payment_p2017_06_customer_id,payment_p2017_06_customer_id_idx} | CREATE INDEX ON public.payment_p2017_06 USING btree (customer_id (6 rows) 

Superset-Indizes


Es kann vorkommen, dass Sie viele Indizes akkumulieren, von denen einer eine Teilmenge der Spalten indiziert, die andere Indizes indizieren. Dies kann entweder wünschenswert sein oder nicht - eine Obermenge kann nur nach Index scannen, was gut ist, aber zu viel Speicherplatz beanspruchen kann, oder die Abfrage, für die diese Obermenge optimiert werden sollte, wird nicht mehr verwendet.

Wenn Sie die Definition solcher Indizes automatisieren müssen, können Sie mit pg_index aus der Tabelle pg_catalog .

Nicht verwendete Indizes


Mit der Entwicklung von Anwendungen, die Datenbanken verwenden, entwickeln sich auch die von ihnen verwendeten Abfragen. Zuvor hinzugefügte Indizes können von keiner Abfrage mehr verwendet werden. Jedes Mal, wenn der Index gescannt wird, wird er vom Statistikmanager markiert. In der Systemkatalogansicht idx_scan Wert idx_scan , bei dem es sich um einen kumulativen Zähler handelt. Wenn Sie diesen Wert über einen bestimmten Zeitraum (z. B. einen Monat) verfolgen, erhalten Sie eine gute Vorstellung davon, welche Indizes nicht verwendet werden und gelöscht werden können.

Hier ist eine Anfrage, um die aktuellen Scan-Zählungen aller Indizes im 'public' Schema abzurufen:

 SELECT relname, indexrelname, idx_scan FROM pg_catalog.pg_stat_user_indexes WHERE schemaname = 'public'; with output like this: pagila=# SELECT relname, indexrelname, idx_scan pagila-# FROM pg_catalog.pg_stat_user_indexes pagila-# WHERE schemaname = 'public' pagila-# LIMIT 10; relname | indexrelname | idx_scan ---------------+--------------------+---------- customer | customer_pkey | 32093 actor | actor_pkey | 5462 address | address_pkey | 660 category | category_pkey | 1000 city | city_pkey | 609 country | country_pkey | 604 film_actor | film_actor_pkey | 0 film_category | film_category_pkey | 0 film | film_pkey | 11043 inventory | inventory_pkey | 16048 (10 rows) 

Erstellen Sie Indizes mit weniger Sperren neu


Oft müssen Indizes neu erstellt werden, wenn sie beispielsweise aufgeblasen sind, und die Neuerstellung kann den Scan beschleunigen. Außerdem können Indizes beschädigt sein. Das Ändern von Indexparametern muss möglicherweise auch neu erstellt werden.

Aktivieren Sie die parallele Indexerstellung


In PostgreSQL 11 ist das Erstellen eines B-Tree-Index wettbewerbsfähig. Um den Erstellungsprozess zu beschleunigen, können mehrere parallele Mitarbeiter verwendet werden. Stellen Sie jedoch sicher, dass diese Konfigurationsparameter richtig eingestellt sind:

 SET max_parallel_workers = 32; SET max_parallel_maintenance_workers = 16; 

Die Standardwerte sind zu klein. Idealerweise sollten diese Zahlen zusammen mit der Anzahl der Prozessorkerne erhöht werden. Lesen Sie die Dokumentation für weitere Details.

Erstellung des Hintergrundindex


Sie können einen Index im Hintergrund mit dem Parameter CONCURRENTLY des Befehls CREATE INDEX erstellen:

 pagila=# CREATE INDEX CONCURRENTLY idx_address1 ON address(district); CREATE INDEX 


Diese Prozedur zur Indexerstellung unterscheidet sich von der üblichen darin, dass sie keine Tabellensperre erfordert und daher keine Schreibvorgänge blockiert. Andererseits dauert es länger und verbraucht mehr Ressourcen.

Postgres bietet viele flexible Optionen zum Erstellen von Indizes und Möglichkeiten zum Lösen bestimmter Fälle sowie Möglichkeiten zum Verwalten der Datenbank im Falle eines explosiven Wachstums Ihrer Anwendung. Wir hoffen, dass diese Tipps Ihnen helfen, Ihre Abfragen schnell und Ihre Datenbank skalierbar zu machen.

Source: https://habr.com/ru/post/de453046/


All Articles