sql >> Database teknologi >  >> RDS >> PostgreSQL

En oversigt over indeksændringerne i PostgreSQL 11

Den rigtige anvendelse af indekser kan gøre forespørgsler lynhurtige.

Indekser bruger pointere til at få adgang til datasider på en hurtig måde.

Store ændringer skete på indekser i PostgreSQL 11, masser af længe ventede patches er blevet frigivet.

Lad os tage et kig på nogle af de fantastiske funktioner i denne udgivelse.

Parallelle B-TREE-indeksbygninger

PostgreSQL 11 introducerede en infrastruktur-patch for at muliggøre oprettelse af parallel indeks.

Det kan kun bruges med B-Tree indeks som nu.

At bygge et parallelt B-Tree-indeks er to til tre gange hurtigere end at gøre det samme uden parallelt arbejde (eller seriel opbygning).

I PostgreSQL 11 er oprettelse af parallel indeks aktiveret som standard.

Der er to vigtige parametre:

  • max_parallel_workers - Indstiller det maksimale antal arbejdere, som systemet kan understøtte for parallelle forespørgsler.
  • max_parallel_maintenance_workers - Styrer det maksimale antal arbejdsprocesser, der kan bruges til at OPRETTE INDEX.

Lad os tjekke det med et eksempel:

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)

Lad os prøve det med 8-vejs parallelt arbejde:

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 præstationsforskellen med parallelarbejderen, mere end 60 % performant med kun en lille ændring. Maintenance_work_mem kan også øges for at få mere ydeevne.

ALTER-tabellen er også med til at øge antallet af parallelarbejdere. Nedenstående syntaks kan bruges til at øge parallelle arbejdere sammen med max_parallel_maintenance_workers. Dette omgår omkostningsmodellen fuldstændigt.

ALTER TABLE test_btree SET (parallel_workers = 24);

Tip:NULSTIL til standard, når indeksopbygningen er fuldført for at forhindre uønsket forespørgselsplan.

CREATE INDEX med muligheden CONCURRENTLY understøtter parallelle builds uden særlige begrænsninger, kun den første tabelscanning udføres faktisk parallelt.

Dybere præstationstest kan findes her.

Tilføj prædikatlåsning for Hash-, Gist- og Gin-indekser

PostgreSQL 11 leveres med prædikatlås-understøttelse for hash-indekser, gin-indekser og hovedindekser. Disse vil gøre SERIALIZABLE transaktionsisolering meget mere effektiv med disse indekser.

Fordel:prædikatlåsning kan give bedre ydeevne på serialiserbart isolationsniveau ved at reducere antallet af falske positive, hvilket fører til unødvendig serialiseringsfejl.

I PostgreSQL 10 er låseområdet relationen, men i PostgreSQL 11 er låsen kun side.

Lad os teste det af.

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 nedenfor, er låsen på sideniveau i stedet for relation. I PostgreSQL 10 var det på relationsniveau, så det er en STOR WIN for samtidige 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)

Tip:En sekventiel scanning vil altid have brug for en prædikatlås på relationsniveau. Dette kan resultere i en øget frekvens af serialiseringsfejl. Det kan være nyttigt at tilskynde til brug af indeksscanninger ved at reducere random_page_cost og/eller øge cpu_tuple_cost.

Tillad VARME opdateringer for nogle udtryksindekser

Heap Only Tuple (HOT)-funktionen eliminerer overflødige indeksindtastninger og tillader genbrug af plads, der er taget af SLETTEDE eller forældede OPDATEREDE tuples uden at udføre et borddækkende vakuum. Det reducerer indeksstørrelsen ved at undgå oprettelse af identiske indeksindgange.

Hvis værdien af ​​et indeksudtryk er uændret efter OPDATERING, tillad HOT opdateringer, hvor PostgreSQL tidligere ikke tillod dem, hvilket giver et betydeligt ydelsesboost i disse tilfælde.

Dette er især nyttigt for indekser såsom JSON->>felt, hvor JSON-værdien ændres, men den indekserede værdi ikke gør det.

Denne funktion blev rullet tilbage i 11.1 på grund af ydeevneforringelse (kun AT Free BSD ifølge Simon), flere detaljer / benchmark kan findes her. Dette bør rettes i fremtidig udgivelse.

Tillad, at hele Hash-indekssider scannes

Hash-indeks:Forespørgselsplanlæggeren vil overveje at bruge et hash-indeks, når en indekseret kolonne er involveret i en sammenligning med =-operatoren. Det var heller ikke nedbrudssikkert (ikke logget på WAL), så det skal genopbygges efter DB-nedbrud, og ændringer til hash blev ikke skrevet via streaming-replikering.

I PostgreSQL 10 blev hash-indekset WAL logget, det betyder, at det er CRASH sikkert og kan replikeres. Hash-indekser bruger meget mindre plads sammenlignet med B-Tree, så de kan passe bedre i hukommelsen.

I PostgreSQL 11 har Btree-indekser en optimering kaldet "single page vacuum", som opportunistisk fjerner døde indeks-pointere fra indekssider, hvilket forhindrer en enorm mængde indeks-bloat, som ellers ville forekomme. Den samme logik er blevet overført til Hash-indekser. Det fremskynder rumgenanvendelse, hvilket reducerer oppustethed.

STATISTIK for funktionsindeks

Det er nu muligt at angive en STATISTICS-værdi for en funktionsindekskolonne. Det er meget værdifuldt for effektiviteten af ​​en specialiseret applikation. Vi kan nu indsamle statistik om udtrykskolonner, som vil hjælpe planlæggeren til at tage en mere præcis beslutning.

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

Et nyt Contrib-modul amcheck er tilføjet. Kun B-Tree indekser kan kontrolleres.

Lad os teste det af!

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 opdelt indeks muligt

Før PostgreSQL11 var det ikke muligt at oprette et indeks på en undertabel eller en opdelt tabel.

I PostgreSQL 11, når CREATE INDEX køres på en partitioneret tabel / overordnet tabel, opretter den katalogindgange for et indeks på den partitionerede tabel og kaskader for at skabe faktiske indekser på de eksisterende partitioner. Det vil også oprette dem i fremtidige partitioner.

Lad os prøve at oprette en overordnet tabel og en partitionere 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')

Lad os prøve at oprette et indeks på den overordnede 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)

Indekset er kaskaderet ned til alle partitionerne i PostgreSQL 11, hvilket er en rigtig fed funktion.

Dækkende indeks (inkluder CLAUSE for indekser)

En INCLUDE-sætning for at tilføje kolonner til indekset kan angives. Dette er effektivt, når du tilføjer kolonner, der ikke er relateret til en unik begrænsning af et unikt indeks. INCLUDE-kolonnerne eksisterer udelukkende for at give flere forespørgsler mulighed for at drage fordel af kun indeksscanninger. Kun B-træ-indekser understøtter INCLUDE-klausulen som nu.

Lad os tjekke adfærden uden INCLUDE. Den bruger ikke kun indeksscanning, hvis der vises yderligere kolonner i SELECT. Dette kan opnås ved at bruge INCLUDE-klausulen.

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)

Lad os prøve med inkludere klausul. I eksemplet nedenfor er den UNIKKE BEGRÆNSNING oprettet i kolonne a og b, men indekset inkluderer en c kolonne.

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)

Der kan ikke være nogen overlapning mellem kolonner i hovedkolonnelisten og dem fra inkluderende listen

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 kolonne brugt med et udtryk i hovedlisten virker:

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

Udtryk kan ikke bruges i en inkluderende liste, fordi de ikke kan bruges i en kun indeksscanning:

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

Konklusion

De nye funktioner i PostgreSQL vil helt sikkert forbedre DBA'ernes liv, så det er på vej til at blive et stærkt alternativt valg i open source DB. Jeg forstår, at nogle få funktioner i indekser i øjeblikket er begrænset til B-Tree, det er stadig en god start på parallel eksekveringsæra for PostgreSQL og på vej til et godt værktøj til at se nærmere på. Tak!


  1. 6 måder at konvertere en streng til en dato/tidsværdi i SQL Server

  2. Postgres FEJL:kunne ikke åbne filen til læsning:Tilladelse nægtet

  3. Hvorfor skulle jeg ikke bruge mysql_* funktioner i PHP?

  4. Holland Access Developer Day 2019 – 14. september