De juiste toepassing van indexen kan zoekopdrachten razendsnel maken.
Indexen gebruiken aanwijzers om snel toegang te krijgen tot gegevenspagina's.
Er zijn grote veranderingen doorgevoerd in Indexen in PostgreSQL 11, er zijn veel langverwachte patches uitgebracht.
Laten we eens kijken naar enkele van de geweldige functies van deze release.
Parallelle B-TREE Index Builds
PostgreSQL 11 heeft een infrastructuurpatch geïntroduceerd om het maken van parallelle indexen mogelijk te maken.
Het kan voorlopig alleen worden gebruikt met de B-Tree-index.
Het bouwen van een parallelle B-Tree-index is twee tot drie keer sneller dan hetzelfde doen zonder parallel te werken (of seriële build).
In PostgreSQL 11 is het maken van parallelle indexen standaard ingeschakeld.
Er zijn twee belangrijke parameters:
- max_parallel_workers - Stelt het maximum aantal werknemers in dat het systeem kan ondersteunen voor parallelle zoekopdrachten.
- max_parallel_maintenance_workers - Bepaalt het maximum aantal werkprocessen dat kan worden gebruikt om INDEX TE MAKEN.
Laten we eens kijken met een voorbeeld:
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)
Laten we het proberen met 8-weg parallel werk:
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)
We kunnen het prestatieverschil zien met de parallelle werker, meer dan 60% performant met slechts een kleine verandering. De maintenance_work_mem kan ook worden verhoogd om meer prestaties te krijgen.
De ALTER-tabel helpt ook om parallelle werkers te vergroten. De onderstaande syntaxis kan worden gebruikt om parallelle werknemers te vergroten, samen met max_parallel_maintenance_workers. Dit omzeilt het kostenmodel volledig.
ALTER TABLE test_btree SET (parallel_workers = 24);
Tip:RESET naar standaard zodra de indexopbouw is voltooid om een ongunstig queryplan te voorkomen.
CREATE INDEX met de optie CONCURRENTLY ondersteunt parallelle builds zonder speciale beperkingen, alleen de eerste tabelscan wordt daadwerkelijk parallel uitgevoerd.
Uitgebreidere prestatietests zijn hier te vinden.
Predicaatvergrendeling toevoegen voor hash-, Gist- en Gin-indexen
PostgreSQL 11 wordt geleverd met ondersteuning voor predikaatvergrendeling voor hash-indexen, gin-indexen en gist-indexen. Deze maken SERIALIZABLE transactie-isolatie veel efficiënter met die indexen.
Voordeel:predikaatvergrendeling kan betere prestaties leveren op serialiseerbaar isolatieniveau door het aantal valse positieven te verminderen, wat leidt tot onnodige serialisatiefouten.
In PostgreSQL 10 is het vergrendelingsbereik de relatie, maar in PostgreSQL 11 blijkt vergrendeling alleen pagina te zijn.
Laten we het uitproberen.
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)
Zoals we hieronder kunnen zien, bevindt de vergrendeling zich op paginaniveau in plaats van op relatie. In PostgreSQL 10 was het op relatieniveau, dus het is een GROTE WIN voor gelijktijdige transacties 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)
Tip:Voor een sequentiële scan is altijd een predikaatslot op relatieniveau nodig. Dit kan resulteren in een verhoogd aantal serialisatiefouten. Het kan nuttig zijn om het gebruik van indexscans aan te moedigen door de random_page_cost te verlagen en/of de cpu_tuple_cost te verhogen.
Sta HOT-updates toe voor sommige expressie-indexen
De Heap Only Tuple (HOT)-functie elimineert overbodige index-invoer en maakt hergebruik mogelijk van ruimte die is ingenomen door VERWIJDERDE of verouderde BIJGEWERKTE tupels zonder een tafelbreed vacuüm uit te voeren. Het verkleint de indexgrootte door het maken van indexitems met identieke sleutels te vermijden.
Als de waarde van een index-expressie ongewijzigd blijft na UPDATE, sta dan HOT-updates toe waar PostgreSQL ze voorheen niet toestond, wat in die gevallen een aanzienlijke prestatieverbetering oplevert.
Dit is vooral handig voor indexen zoals JSON->>veld waar de JSON-waarde verandert, maar de geïndexeerde waarde niet.
Deze functie is in 11.1 teruggedraaid vanwege prestatievermindering (AT Free BSD alleen volgens Simon), meer details / benchmark zijn hier te vinden. Dit zou in een toekomstige release moeten worden opgelost.
Toestaan dat volledige hash-indexpagina's worden gescand
Hash-index:De queryplanner zal overwegen om een hash-index te gebruiken wanneer een geïndexeerde kolom betrokken is bij een vergelijking met de =-operator. Het was ook niet crashveilig (niet ingelogd in WAL), dus het moet opnieuw worden opgebouwd na DB-crashes en wijzigingen in de hash zijn niet geschreven via streamingreplicatie.
In PostgreSQL 10 was de hash-index WAL-gelogd, wat betekent dat het CRASH-veilig is en kan worden gerepliceerd. Hash-indexen gebruiken veel minder ruimte in vergelijking met B-Tree, zodat ze beter in het geheugen passen.
In PostgreSQL 11 hebben Btree-indexen een optimalisatie genaamd "single page vacuum", die opportunistisch dode indexwijzers van indexpagina's verwijdert, waardoor een enorme hoeveelheid indexbloat wordt voorkomen, die anders zou optreden. Dezelfde logica is overgezet naar Hash-indexen. Het versnelt de recycling van ruimte en vermindert opgeblazen gevoel.
STATISTIEKEN van functie-index
Het is nu mogelijk om een STATISTICS-waarde op te geven voor een functie-indexkolom. Het is zeer waardevol voor de efficiëntie van een gespecialiseerde toepassing. We kunnen nu statistieken over uitdrukkingskolommen verzamelen, die de planner zullen helpen een nauwkeurigere beslissing te nemen.
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"
controleer
Er is een nieuwe bijdragemodule amcheck toegevoegd. Alleen B-Tree-indexen kunnen worden gecontroleerd.
Laten we het uitproberen!
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.
Lokale gepartitioneerde index mogelijk
Vóór PostgreSQL11 was het niet mogelijk om een index te maken op een onderliggende tabel of een gepartitioneerde tabel.
In PostgreSQL 11, wanneer CREATE INDEX wordt uitgevoerd op een gepartitioneerde tabel / bovenliggende tabel, maakt het catalogusitems voor een index op de gepartitioneerde tabel en cascades om daadwerkelijke indexen te maken op de bestaande partities. Het zal ze ook in toekomstige partities maken.
Laten we proberen een bovenliggende tabel te maken en deze te partitioneren:
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')
Laten we proberen een index te maken op de bovenliggende tabel:
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)
De index wordt getrapt naar alle partities in PostgreSQL 11, wat echt een coole functie is.
Dekkingsindex (inclusief CLAUSULE voor indexen)
Er kan een INCLUDE-clausule worden opgegeven om kolommen aan de index toe te voegen. Dit is effectief bij het toevoegen van kolommen die geen verband houden met een unieke beperking van een unieke index. De INCLUDE-kolommen bestaan uitsluitend om meer zoekopdrachten te laten profiteren van scans met alleen index. Voorlopig ondersteunen alleen B-tree-indexen de INCLUDE-clausule.
Laten we het gedrag eens bekijken zonder INCLUDE. Het zal geen index alleen scan gebruiken als er extra kolommen verschijnen in de SELECT. Dit kan worden bereikt door de INCLUDE-clausule te gebruiken.
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)
Laten we eens proberen met de include-clausule. In het onderstaande voorbeeld wordt de UNIEKE BEPERKING gemaakt in de kolommen a en b, maar de index bevat een kolom 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)
Er mag geen overlap zijn tussen kolommen in de hoofdkolomlijst en die in de include-lijst
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
Een kolom die wordt gebruikt met een uitdrukking in de hoofdlijst werkt:
severalnines=# CREATE UNIQUE INDEX new_unique_idx_2 ON with_include(round(a), b) INCLUDE (a);
CREATE INDEX
Expressies kunnen niet worden gebruikt in een include-lijst omdat ze niet kunnen worden gebruikt in een scan met alleen index:
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
Conclusie
De nieuwe functies van PostgreSQL zullen het leven van DBA's zeker verbeteren, dus het is op weg om een sterke alternatieve keuze te worden in open source DB. Ik begrijp dat een paar kenmerken van indexen momenteel beperkt zijn tot B-Tree, het is nog steeds een goed begin van het parallelle uitvoeringstijdperk voor PostgreSQL en op weg naar een leuke tool om goed te bekijken. Bedankt!