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

Ein Überblick über die Indexänderungen in PostgreSQL 11

Die richtige Anwendung von Indizes kann Abfragen blitzschnell machen.

Indizes verwenden Zeiger, um schnell auf Datenseiten zuzugreifen.

In PostgreSQL 11 wurden große Änderungen an Indizes vorgenommen, viele lang erwartete Patches wurden veröffentlicht.

Werfen wir einen Blick auf einige der großartigen Funktionen dieser Version.

Parallele B-TREE-Indexaufbauten

PostgreSQL 11 hat einen Infrastruktur-Patch eingeführt, um die parallele Indexerstellung zu ermöglichen.

Es kann derzeit nur mit dem B-Tree-Index verwendet werden.

Das Erstellen eines parallelen B-Tree-Index ist zwei- bis dreimal schneller, als dasselbe ohne paralleles Arbeiten (oder serielles Erstellen) zu tun.

In PostgreSQL 11 ist die parallele Indexerstellung standardmäßig aktiviert.

Es gibt zwei wichtige Parameter:

  • max_parallel_workers – Legt die maximale Anzahl von Workern fest, die das System für parallele Abfragen unterstützen kann.
  • max_parallel_maintenance_workers - Steuert die maximale Anzahl von Worker-Prozessen, die zum CREATE INDEX verwendet werden können.

Lassen Sie es uns anhand eines Beispiels überprüfen:

severalnines=# CREATE TABLE test_btree AS SELECT generate_series(1,100000000) AS id;
SELECT 100000000
severalnines=#  SET maintenance_work_mem = '1GB';
severalnines=# \timing
severalnines=#  CREATE INDEX q ON test_btree (id);
TIME: 25294.185 ms (00:25.294)

Versuchen wir es mit 8-Wege-Parallelarbeit:

severalnines=# SET maintenance_work_mem = '2GB';
severalnines=# SET max_parallel_workers = 16;
severalnines=# SET max_parallel_maintenance_workers = 8;
severalnines=# \timing
severalnines=# CREATE INDEX q1 ON test_btree (id);
TIME: 11001.240 ms (00:11.001)

Wir können den Leistungsunterschied mit dem Parallel Worker sehen, mehr als 60 % Leistung mit nur einer kleinen Änderung. Das maintenance_work_mem kann auch erhöht werden, um mehr Leistung zu erhalten.

Die ALTER-Tabelle hilft auch, parallele Worker zu erhöhen. Die folgende Syntax kann verwendet werden, um parallele Worker zusammen mit max_parallel_maintenance_workers zu erhöhen. Damit wird das Kostenmodell komplett umgangen.

ALTER TABLE test_btree SET (parallel_workers = 24);

Tipp:Auf Standard zurücksetzen, sobald die Indexerstellung abgeschlossen ist, um einen ungünstigen Abfrageplan zu verhindern.

CREATE INDEX mit der Option CONCURRENTLY unterstützt parallele Builds ohne besondere Einschränkungen, nur der erste Tabellenscan wird tatsächlich parallel durchgeführt.

Ausführlichere Leistungstests finden Sie hier.

Prädikatsperre für Hash-, Gist- und Gin-Indizes hinzufügen

PostgreSQL 11 wird mit Unterstützung für Prädikatsperren für Hash-Indizes, Gin-Indizes und Gist-Indizes ausgeliefert. Dadurch wird die SERIALIZABLE-Transaktionsisolation mit diesen Indizes viel effizienter.

Vorteil:Die Prädikatsperre kann eine bessere Leistung auf serialisierbarer Isolationsebene bieten, indem die Anzahl falsch positiver Ergebnisse reduziert wird, was zu unnötigen Serialisierungsfehlern führt.

In PostgreSQL 10 ist der Sperrbereich die Relation, aber in PostgreSQL 11 ist die Sperre nur eine Seite.

Lass es uns testen.

severalnines=# CREATE TABLE sv_predicate_lock1(c1 INT, c2 VARCHAR(10)) ;
CREATE TABLE
severalnines=# CREATE INDEX idx1_sv_predicate_lock1 ON sv_predicate_lock1 USING 'hash(c1) ;
CREATE INDEX
severalnines=# INSERT INTO sv_predicate_lock1 VALUES (generate_series(1, 100000),  'puja') ;
INSERT 0 100000
severalnines=#  BEGIN ISOLATION LEVEL SERIALIZABLE ;
BEGIN
severalnines=# SELECT * FROM sv_predicate_lock1 WHERE c1=10000 FOR UPDATE ;
  c1   |  c2
-------+-------
 10000 | puja
(1 row)

Wie wir unten sehen können, befindet sich die Sperre auf Seitenebene und nicht auf Beziehung. In PostgreSQL 10 war es auf Beziehungsebene, also ist es ein GROSSER GEWINN für gleichzeitige Transaktionen in PostgreSQL 11.

severalnines=# SELECT locktype, relation::regclass, mode FROM pg_locks ;
   locktype    |        relation         |      mode
---------------+-------------------------+-----------------
 relation      | pg_locks                | AccessShareLock
 relation      | idx1_sv_predicate_lock1 | AccessShareLock
 relation      | sv_predicate_lock1      | RowShareLock
 virtualxid    |                         | ExclusiveLock
 transactionid |                         | ExclusiveLock
 page          | idx1_sv_predicate_lock1 | SIReadLock
 tuple         | sv_predicate_lock1      | SIReadLock
(7 rows)

Tipp:Ein sequenzieller Scan benötigt immer eine Prädikatsperre auf Beziehungsebene. Dies kann zu einer erhöhten Rate von Serialisierungsfehlern führen. Es kann hilfreich sein, die Verwendung von Index-Scans zu fördern, indem Sie random_page_cost reduzieren und/oder cpu_tuple_cost erhöhen.

HOT-Updates für einige Ausdrucksindizes zulassen

Die Funktion Heap Only Tuple (HOT) eliminiert redundante Indexeinträge und ermöglicht die Wiederverwendung von Speicherplatz, der von gelöschten oder veralteten UPDATE-Tupeln belegt wird, ohne eine tabellenweite Bereinigung durchzuführen. Es reduziert die Indexgröße, indem es die Erstellung von Indexeinträgen mit identischen Schlüsseln vermeidet.

Wenn der Wert eines Indexausdrucks nach UPDATE unverändert bleibt, lassen Sie HOT-Updates zu, wo PostgreSQL sie zuvor nicht zugelassen hat, was in diesen Fällen zu einer erheblichen Leistungssteigerung führt.

Dies ist besonders nützlich für Indizes wie JSON->>Felder, bei denen sich der JSON-Wert ändert, der indizierte Wert jedoch nicht.

Dieses Feature wurde in 11.1 aufgrund von Leistungseinbußen zurückgenommen (AT Free BSD nur laut Simon), weitere Details / Benchmark finden Sie hier. Dies sollte in zukünftigen Versionen behoben werden.

Das Scannen ganzer Hash-Indexseiten zulassen

Hash-Index:Der Abfrageplaner erwägt die Verwendung eines Hash-Index, wenn eine indizierte Spalte an einem Vergleich mit dem =-Operator beteiligt ist. Es war auch nicht absturzsicher (nicht in WAL angemeldet), daher muss es nach einem DB-Absturz neu erstellt werden, und Änderungen am Hash wurden nicht über die Streaming-Replikation geschrieben.

In PostgreSQL 10 wurde der Hash-Index WAL-protokolliert, das heißt, er ist CRASH-sicher und kann repliziert werden. Hash-Indizes benötigen im Vergleich zu B-Tree viel weniger Speicherplatz, sodass sie besser in den Speicher passen.

In PostgreSQL 11 verfügen Btree-Indizes über eine Optimierung namens „Single Page Vacuum“, die opportunistisch tote Indexzeiger von Indexseiten entfernt und so eine enorme Indexaufblähung verhindert, die andernfalls auftreten würde. Dieselbe Logik wurde auf Hash-Indizes portiert. Es beschleunigt das Space-Recycling und reduziert das Aufblähen.

STATISTIK des Funktionsindex

Es ist jetzt möglich, einen STATISTICS-Wert für eine Funktionsindexspalte anzugeben. Es ist sehr wertvoll für die Effizienz einer spezialisierten Anwendung. Wir können jetzt Statistiken zu Ausdrucksspalten sammeln, die dem Planer helfen, eine genauere Entscheidung zu treffen.

severalnines=# CREATE INDEX idx1_stats ON stat ((s1 + s2)) ;
CREATE INDEX
severalnines=# ALTER INDEX idx1_stats ALTER COLUMN 1 SET STATISTICS 1000 ;
ALTER INDEX
severalnines=# \d+ idx1_stats
 Index "public.idx1_stats"
Column | Type | Definition | Storage | Stats target
--------+---------+------------+---------+--------------
expr | numeric | (c1 + c2) | main | 1000
btree, for table "public.stat1"

Amcheck

Ein neues Contrib-Modul amcheck wurde hinzugefügt. Es können nur B-Tree-Indizes geprüft werden.

Lass es uns testen!

severalnines=# CREATE EXTENSION amcheck ;
CREATE EXTENSION
severalnines=# SELECT bt_index_check('idx1_stats') ;
ERROR: invalid page in block 0 of relation base/16385/16580
severalnines=#CREATE INDEX idx1_hash_data1 ON data1 USING hash (c1) ;
CREATE INDEX
severalnines=# SELECT bt_index_check('idx1_hash_data1') ;
ERROR: only B-Tree indexes are supported as targets for verification
DETAIL: Relation "idx1_hash_data1" is not a B-Tree index.

Lokaler partitionierter Index möglich

Vor PostgreSQL11 war es nicht möglich, einen Index für eine untergeordnete Tabelle oder eine partitionierte Tabelle zu erstellen.

Wenn in PostgreSQL 11 CREATE INDEX auf einer partitionierten Tabelle/übergeordneten Tabelle ausgeführt wird, erstellt es Katalogeinträge für einen Index auf der partitionierten Tabelle und kaskadiert, um tatsächliche Indizes auf den vorhandenen Partitionen zu erstellen. Es wird sie auch in zukünftigen Partitionen erstellen.

Lassen Sie uns versuchen, eine übergeordnete Tabelle zu erstellen und diese zu partitionieren:

severalnines=# create table test_part ( a int, list varchar(5) ) partition by list (list);
CREATE TABLE
severalnines=# create table part_1 partition of test_part for values in ('India');
CREATE TABLE
severalnines=# create table part_2 partition of test_part for values in ('USA');
CREATE TABLE
severalnines=#
severalnines=# \d+ test_part
                                        Table "public.test_part"
 Column |         Type         | Collation | Nullable | Default | Storage  | Stats target | Description
--------+----------------------+-----------+----------+---------+----------+--------------+-------------
 a      | integer              |           |          |         | plain    |              |
 list   | character varying(5) |           |          |         | extended |              |
Partition key: LIST (list)
Partitions: part_1 FOR VALUES IN ('India'),
            part_2 FOR VALUES IN ('USA')

Versuchen wir, einen Index für die übergeordnete Tabelle zu erstellen:

severalnines=# create index i_test on test_part (a);
CREATE INDEX
severalnines=# \d part_2
                     Table "public.part_2"
 Column |         Type         | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
 a      | integer              |           |          |
 list   | character varying(5) |           |          |
Partition of: test_part FOR VALUES IN ('USA')
Indexes:
    "part_2_a_idx" btree (a)

severalnines=# \d part_1
                     Table "public.part_1"
 Column |         Type         | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
 a      | integer              |           |          |
 list   | character varying(5) |           |          |
Partition of: test_part FOR VALUES IN ('India')
Indexes:
    "part_1_a_idx" btree (a)

Der Index wird in PostgreSQL 11 auf alle Partitionen kaskadiert, was eine wirklich coole Funktion ist.

Abdeckender Index (KLAUSELN für Indizes einschließen)

Eine INCLUDE-Klausel zum Hinzufügen von Spalten zum Index kann angegeben werden. Dies ist effektiv, wenn Spalten hinzugefügt werden, die nicht mit einer Eindeutigkeitsbeschränkung eines eindeutigen Indexes zusammenhängen. Die INCLUDE-Spalten sind nur vorhanden, damit mehr Abfragen von Nur-Index-Scans profitieren können. Derzeit unterstützen nur B-Tree-Indizes die INCLUDE-Klausel.

Lassen Sie uns das Verhalten ohne INCLUDE überprüfen. Es wird kein Index-Only-Scan verwendet, wenn zusätzliche Spalten in der SELECT-Datei erscheinen. Dies kann durch die Verwendung der INCLUDE-Klausel erreicht werden.

severalnines=# CREATE TABLE no_include (a int, b int, c int);
CREATE TABLE
severalnines=# INSERT INTO no_include SELECT 3 * val, 3 * val + 1, 3 * val + 2 FROM generate_series(0, 1000000) as val;
INSERT 0 1000001
severalnines=# CREATE UNIQUE INDEX old_unique_idx ON no_include(a, b);
CREATE INDEX
severalnines=# VACUUM ANALYZE;
VACUUM
EXPLAIN ANALYZE SELECT a, b FROM no_include WHERE a < 1000;  - It will do index only scan 
EXPLAIN ANALYZE SELECT a, b, c FROM no_include WHERE a < 1000; - It will not do index only scan as we have extra column in select. 
severalnines=# CREATE INDEX old_idx ON no_include (a, b, c);
CREATE INDEX
severalnines=# VACUUM ANALYZE;
VACUUM
severalnines=# EXPLAIN ANALYZE SELECT a, b, c FROM no_include WHERE a < 1000;   - It did index only scan as index on all three columns. 
                     QUERY PLAN
-------------------------------------------------
 Index Only Scan using old_idx on no_include
     (cost=0.42..14.92 rows=371 width=12)
     (actual time=0.086..0.291 rows=334 loops=1)
   Index Cond: (a < 1000)
   Heap Fetches: 0
 Planning Time: 2.108 ms
 Execution Time: 0.396 ms
(5 rows)

Versuchen wir es mit der Include-Klausel. Im Beispiel unten wird die UNIQUE CONSTRAINT in den Spalten a und b erstellt, aber der Index enthält eine c-Spalte.

severalnines=# CREATE TABLE with_include (a int, b int, c int);
CREATE TABLE
severalnines=# INSERT INTO with_include SELECT 3 * val, 3 * val + 1, 3 * val + 2 FROM generate_series(0, 1000000) as val;
INSERT 0 1000001
severalnines=# CREATE UNIQUE INDEX new_unique_idx ON with_include(a, b) INCLUDE (c);
CREATE INDEX
severalnines=# VACUUM ANALYZE;
VACUUM
severalnines=#  EXPLAIN ANALYZE SELECT a, b, c FROM with_include WHERE a < 10000;
                       QUERY PLAN
-----------------------------------------------------
 Index Only Scan using new_unique_idx on with_include
     (cost=0.42..116.06 rows=3408 width=12)
     (actual time=0.085..2.348 rows=3334 loops=1)
   Index Cond: (a < 10000)
   Heap Fetches: 0
 Planning Time: 1.851 ms
 Execution Time: 2.840 ms
(5 rows)

Es darf keine Überschneidung zwischen Spalten in der Hauptspaltenliste und denen aus der Include-Liste geben

severalnines=# CREATE UNIQUE INDEX new_unique_idx ON with_include(a, b) INCLUDE (a);
ERROR:  42P17: included columns must not intersect with key columns
LOCATION:  DefineIndex, indexcmds.c:373

Eine Spalte, die mit einem Ausdruck in der Hauptliste verwendet wird, funktioniert:

severalnines=# CREATE UNIQUE INDEX new_unique_idx_2 ON with_include(round(a), b) INCLUDE (a);
CREATE INDEX

Ausdrücke können nicht in einer Include-Liste verwendet werden, da sie nicht in einem Nur-Index-Scan verwendet werden können:

severalnines=# CREATE UNIQUE INDEX new_unique_idx_2 ON with_include(a, b) INCLUDE (round(c));
ERROR:  0A000: expressions are not supported in included columns
LOCATION:  ComputeIndexAttrs, indexcmds.c:1446

Schlussfolgerung

Die neuen Funktionen von PostgreSQL werden das Leben von DBAs sicherlich verbessern, sodass es auf dem Weg ist, eine starke Alternative in der Open-Source-DB zu werden. Ich verstehe, dass einige Funktionen von Indizes derzeit auf B-Tree beschränkt sind, es ist immer noch ein großartiger Start in die Ära der parallelen Ausführung für PostgreSQL und auf dem Weg zu einem netten Tool, um genau hinzusehen. Danke!