Správná aplikace indexů může urychlit dotazy.
Indexy používají ukazatele pro rychlý přístup k datovým stránkám.
V Indexech v PostgreSQL 11 došlo k velkým změnám, bylo vydáno mnoho očekávaných oprav.
Pojďme se podívat na některé skvělé funkce tohoto vydání.
Paralelní sestavení indexu B-TREE
PostgreSQL 11 zavedl opravu infrastruktury, která umožňuje paralelní vytváření indexů.
Lze jej použít pouze s indexem B-Stromu jako nyní.
Vytvoření paralelního indexu B-Stromu je dvakrát až třikrát rychlejší než provádění stejné věci bez paralelní práce (nebo sériového sestavení).
V PostgreSQL 11 je vytváření paralelního indexu ve výchozím nastavení zapnuto.
Jsou zde dva důležité parametry:
- max_parallel_workers – Nastavuje maximální počet pracovníků, které může systém podporovat pro paralelní dotazy.
- max_parallel_maintenance_workers – řídí maximální počet pracovních procesů, které lze použít k VYTVOŘENÍ INDEXu.
Pojďme si to ověřit na příkladu:
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)
Zkusme to s 8cestnou paralelní prací:
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)
Můžeme vidět rozdíl ve výkonu s paralelním pracovníkem, který má více než 60% výkon pouze s malou změnou. Udržovací_práce_mem lze také zvýšit, abyste získali vyšší výkon.
Stůl ALTER také pomáhá zvýšit paralelní pracovníky. Níže uvedená syntaxe může být použita ke zvýšení počtu paralelních pracovníků spolu s max_parallel_maintenance_workers. Tím se zcela obchází nákladový model.
ALTER TABLE test_btree SET (parallel_workers = 24);
Tip:Po dokončení sestavení indexu RESETUJTE na výchozí nastavení, abyste zabránili nepříznivému plánu dotazů.
CREATE INDEX s možností CONCURRENTLY podporuje paralelní sestavení bez zvláštních omezení, pouze první skenování tabulky se skutečně provádí paralelně.
Hlubší výkonnostní testy naleznete zde.
Přidat zamykání predikátu pro indexy hash, gist a gin
PostgreSQL 11 se dodává s podporou predikátového zámku pro hash indexy, gin indexy a gist indexy. Díky tomu bude izolace transakcí SERIALIZABLE s těmito indexy mnohem efektivnější.
Výhoda:predikátové zamykání může poskytnout lepší výkon na úrovni serializovatelné izolace snížením počtu falešných poplachů, což vede ke zbytečnému selhání serializace.
V PostgreSQL 10 je rozsahem zámku vztah, ale v PostgreSQL 11 je zámek pouze stránkový.
Pojďme to vyzkoušet.
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)
Jak můžeme vidět níže, zámek je na úrovni stránky místo vztahu. V PostgreSQL 10 to bylo na úrovni vztahu, takže je to VELKÁ VÝHRA pro souběžné transakce v 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)
Tip:Sekvenční skenování bude vždy vyžadovat predikátový zámek na úrovni vztahu. To může vést ke zvýšené míře selhání serializace. Může být užitečné podpořit používání prohledávání indexů snížením random_page_cost a/nebo zvýšením cpu_tuple_cost.
Povolit HOT aktualizace pro některé indexy výrazů
Funkce Heap Only Tuple (HOT) odstraňuje nadbytečné položky indexu a umožňuje opětovné využití prostoru zabraného DELETEd nebo zastaralými AKTUALIZOVANÝMI n-ticemi, aniž by bylo nutné provést vakuum v celé tabulce. Zmenšuje velikost indexu tím, že zabraňuje vytváření identicky zakódovaných položek indexu.
Pokud se hodnota indexového výrazu po UPDATE nezmění, povolte HOT aktualizace tam, kde je dříve PostgreSQL nepovoloval, což v těchto případech výrazně zvýší výkon.
To je užitečné zejména pro indexy, jako je pole JSON->>, kde se hodnota JSON mění, ale indexovaná hodnota ne.
Tato funkce byla vrácena zpět ve verzi 11.1 kvůli snížení výkonu (pouze AT Free BSD podle Simona), více podrobností / benchmark naleznete zde. To by mělo být opraveno v budoucí verzi.
Povolit skenování celých indexových stránek hash
Index hash:Plánovač dotazů zváží použití indexu hash vždy, když se indexovaný sloupec účastní porovnávání pomocí operátoru =. Nebylo také bezpečné proti havárii (nepřihlášeno do WAL), takže je třeba jej znovu sestavit po zhroucení DB a změny hash nebyly zapsány prostřednictvím streamované replikace.
V PostgreSQL 10 byl hash index protokolován WAL, to znamená, že je bezpečný pro CRASH a lze jej replikovat. Hash indexy využívají mnohem méně místa než B-Strom, takže se lépe vejdou do paměti.
V PostgreSQL 11 mají indexy Btree optimalizaci nazvanou „single page vakuum“, která příležitostně odstraňuje mrtvé ukazatele indexu z indexových stránek, čímž zabraňuje obrovskému množství indexového nadýmání, ke kterému by jinak došlo. Stejná logika byla přenesena do hash indexů. Urychluje recyklaci prostoru a snižuje nadýmání.
STATISTIKA indexu funkcí
Nyní je možné zadat hodnotu STATISTICS pro sloupec indexu funkce. Je to velmi cenné pro efektivitu specializované aplikace. Nyní můžeme shromažďovat statistiky o sloupcích výrazů, které pomohou plánovači učinit přesnější rozhodnutí.
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
Byl přidán nový modul Contrib amcheck. Kontrolovat lze pouze indexy B-stromu.
Pojďme to vyzkoušet!
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.
Je možný místní dělený index
Před PostgreSQL11 nebylo možné vytvořit index na podřízené tabulce nebo rozdělené tabulce.
V PostgreSQL 11, když je CREATE INDEX spuštěn na dělené tabulce / nadřazené tabulce, vytváří položky katalogu pro index na dělené tabulce a kaskáduje k vytvoření skutečných indexů na existujících oddílech. Vytvoří je také v budoucích oddílech.
Zkusme vytvořit nadřazenou tabulku a rozdělit ji:
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')
Zkusme vytvořit index na nadřazené tabulce:
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)
Index je v PostgreSQL 11 kaskádovitě seřazen do všech oddílů, což je opravdu skvělá funkce.
Krycí index (včetně CLAUSE pro indexy)
Lze zadat klauzuli INCLUDE pro přidání sloupců do indexu. To je účinné při přidávání sloupců, které nesouvisejí s jedinečným omezením jedinečného indexu. Sloupce INCLUDE existují pouze proto, aby umožnily většímu počtu dotazů využívat skenování pouze na základě indexu. Pouze indexy B-stromu podporují klauzuli INCLUDE jako nyní.
Pojďme zkontrolovat chování bez INCLUDE. Pokud se ve SELECTu objeví další sloupce, nepoužije pouze skenování indexu. Toho lze dosáhnout použitím klauzule INCLUDE.
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)
Zkusme to s klauzulí include. V níže uvedeném příkladu je UNIQUE CONSTRAINT vytvořen ve sloupcích a a b, ale index obsahuje sloupec c.
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)
Mezi sloupci v hlavním seznamu sloupců a sloupci ze seznamu zahrnutí se nesmí překrývat
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
Sloupec použitý s výrazem v hlavním seznamu funguje:
severalnines=# CREATE UNIQUE INDEX new_unique_idx_2 ON with_include(round(a), b) INCLUDE (a);
CREATE INDEX
Výrazy nelze použít v seznamu začlenění, protože je nelze použít při skenování pouze s indexem:
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
Závěr
Nové funkce PostgreSQL jistě zlepší životy správců databází, takže se z něj stane silná alternativa v open source DB. Chápu, že několik funkcí indexů je v současné době omezeno na B-Strom, je to stále skvělý začátek éry paralelního provádění pro PostgreSQL a míří k pěknému nástroji, který si můžete prohlédnout zblízka. Díky!