sql >> Databasteknik >  >> RDS >> PostgreSQL

En översikt över indexändringarna i PostgreSQL 11

Rätt tillämpning av index kan göra förfrågningar blixtrande snabba.

Index använder pekare för att komma åt datasidor på ett snabbt sätt.

Stora förändringar hände på Index i PostgreSQL 11, massor av efterlängtade patchar har släppts.

Låt oss ta en titt på några av de fantastiska funktionerna i den här utgåvan.

Parallell B-TREE Index Builds

PostgreSQL 11 introducerade en infrastrukturpatch för att möjliggöra parallellt indexskapande.

Den kan endast användas med B-Tree index som för närvarande.

Att bygga ett parallellt B-Tree-index är två till tre gånger snabbare än att göra samma sak utan parallellt arbete (eller seriebyggnad).

I PostgreSQL 11 är skapande av parallellt index på som standard.

Det finns två viktiga parametrar:

  • max_parallel_workers – Anger det maximala antalet arbetare som systemet kan stödja för parallella frågor.
  • max_parallel_maintenance_workers - Styr det maximala antalet arbetsprocesser som kan användas för att SKAPA INDEX.

Låt oss kontrollera det med ett exempel:

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)

Låt oss prova det med 8-vägs parallellt arbete:

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)

Vi kan se prestationsskillnaden med parallellarbetaren, mer än 60 % presterande med bara en liten förändring. Maintenance_work_mem kan också utökas för att få bättre prestanda.

ALTER-tabellen hjälper också till att öka antalet parallellarbetare. Syntaxen nedan kan användas för att öka parallella arbetare tillsammans med max_parallel_maintenance_workers. Detta går helt förbi kostnadsmodellen.

ALTER TABLE test_btree SET (parallel_workers = 24);

Tips:ÅTERSTÄLL till standard när indexuppbyggnaden är klar för att förhindra negativ frågeplan.

CREATE INDEX med alternativet CONCURRENTLY stöder parallellbyggen utan särskilda begränsningar, endast den första tabellskanningen utförs faktiskt parallellt.

Djupare prestandatester finns här.

Lägg till predikatlåsning för Hash-, Gist- och Ginindex

PostgreSQL 11 levereras med predikatlåsstöd för hashindex, ginindex och gistindex. Dessa kommer att göra SERIALIZABLE transaktionsisolering mycket effektivare med dessa index.

Fördel:predikatlåsning kan ge bättre prestanda på serialiserbar isoleringsnivå genom att minska antalet falska positiva, vilket leder till onödiga serialiseringsfel.

I PostgreSQL 10 är låsintervallet relationen, men i PostgreSQL 11 är låset endast en sida.

Låt oss testa det.

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)

Som vi kan se nedan är låset på sidnivå istället för relation. I PostgreSQL 10 var det på relationsnivå, så det är en STOR VINST för samtidiga transaktioner i 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)

Tips:En sekventiell skanning kommer alltid att behöva ett predikatlås på relationsnivå. Detta kan resultera i en ökad frekvens av serialiseringsfel. Det kan vara bra att uppmuntra användningen av indexsökningar genom att minska random_page_cost och/eller öka cpu_tuple_cost.

Tillåt HETA uppdateringar för vissa uttrycksindex

Funktionen Heap Only Tuple (HOT) eliminerar redundanta indexposter och tillåter återanvändning av utrymme som tagits av DELETED eller föråldrade UPPDATERADE tuplar utan att utföra ett tabellomfattande vakuum. Det minskar indexstorleken genom att undvika skapandet av identiska indexposter.

Om värdet på ett indexuttryck är oförändrat efter UPPDATERING, tillåt HETA uppdateringar där PostgreSQL tidigare inte tillåtit dem, vilket ger en betydande prestandaökning i dessa fall.

Detta är särskilt användbart för index som JSON->>fält där JSON-värdet ändras men det indexerade värdet inte gör det.

Den här funktionen rullades tillbaka i 11.1 på grund av prestandaförsämring (endast AT Free BSD enligt Simon), mer detaljer / benchmark finns här. Detta bör åtgärdas i framtida versioner.

Tillåt att hela Hash-indexsidor skannas

Hashindex:Frågeplaneraren överväger att använda ett hashindex närhelst en indexerad kolumn är involverad i en jämförelse med operatorn =. Den var inte heller kraschsäker (inte inloggad i WAL) så den måste byggas om efter DB-kraschar, och ändringar i hash skrevs inte via streamingreplikering.

I PostgreSQL 10 var hash-indexet WAL-loggat, det betyder att det är CRASH-säkert och kan replikeras. Hashindex använder mycket mindre utrymme jämfört med B-Tree så att de kan passa bättre i minnet.

I PostgreSQL 11 har Btree-indexen en optimering som kallas "single page vacuum", som opportunistiskt tar bort döda indexpekare från indexsidor, vilket förhindrar en enorm mängd indexbloat, som annars skulle inträffa. Samma logik har porterats till Hash-index. Det påskyndar utrymmesåtervinning, vilket minskar uppblåsthet.

STATISTIK för funktionsindex

Det är nu möjligt att ange ett STATISTICS-värde för en funktionsindexkolumn. Det är mycket värdefullt för effektiviteten i en specialiserad applikation. Vi kan nu samla in statistik om uttryckskolumner, som hjälper planeraren att fatta ett mer exakt beslut.

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

En ny Contrib-modul amcheck har lagts till. Endast B-Tree-index kan kontrolleras.

Låt oss testa det!

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.

Lokalt partitionerat index möjligt

Före PostgreSQL11 var det inte möjligt att skapa ett index på en underordnad tabell eller en partitionerad tabell.

I PostgreSQL 11, när CREATE INDEX körs på en partitionerad tabell / överordnad tabell, skapar den katalogposter för ett index på den partitionerade tabellen och kaskader för att skapa faktiska index på de befintliga partitionerna. Det kommer att skapa dem i framtida partitioner också.

Låt oss försöka skapa en överordnad tabell och en partitionera den:

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')

Låt oss försöka skapa ett index på den överordnade tabellen:

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)

Indexet är kaskadkopplat till alla partitioner i PostgreSQL 11, vilket är en riktigt cool funktion.

Täckande index (inkludera CLAUSE för index)

En INCLUDE-sats för att lägga till kolumner till indexet kan anges. Detta är effektivt när du lägger till kolumner som inte är relaterade till en unik begränsning av ett unikt index. INKLUDERA-kolumnerna finns enbart för att fler frågor ska kunna dra nytta av enbart indexsökningar. Endast B-trädindex stöder INCLUDE-satsen som för närvarande.

Låt oss kontrollera beteendet utan INCLUDE. Den kommer inte att använda endast index-skanning om ytterligare kolumner visas i SELECT. Detta kan uppnås genom att använda INCLUDE-satsen.

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)

Låt oss försöka med inkludera klausul. I exemplet nedan skapas den UNIKA BEGRÄNSNINGEN i kolumnerna a och b, men indexet inkluderar en c-kolumn.

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)

Det kan inte finnas någon överlappning mellan kolumner i huvudkolumnlistan och de från inkluderingslistan

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

En kolumn som används med ett uttryck i huvudlistan fungerar:

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

Uttryck kan inte användas i en inkluderingslista eftersom de inte kan användas i en endast indexsökning:

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

Slutsats

De nya funktionerna i PostgreSQL kommer säkerligen att förbättra livet för DBA:er så det är på väg att bli ett starkt alternativ i DB med öppen källkod. Jag förstår att några funktioner i index för närvarande är begränsade till B-Tree, det är fortfarande en bra start på parallellkörningseran för PostgreSQL och på väg till ett trevligt verktyg att titta närmare på. Tack!


  1. Hur man subtraherar timmar från ett Datetime-värde i MariaDB

  2. En översikt över Just-in-Time Compilation (JIT) för PostgreSQL

  3. Kan IN-operatören använda LIKE-jokertecken (%) i Oracle?

  4. Hur använder man GROUP BY för att sammanfoga strängar i MySQL?