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

Partitioneringsforbedringer i PostgreSQL 11

Et partitioneringssystem i PostgreSQL blev først tilføjet i PostgreSQL 8.1 af 2ndQuadrant-grundlæggeren Simon Riggs . Det var baseret på relationsarv og brugte en ny teknik til at udelukke tabeller fra at blive scannet af en forespørgsel, kaldet "begrænsningsekskludering". Selvom det var et stort fremskridt på det tidspunkt, ses det i dag som besværligt at bruge såvel som langsom og trænger derfor til udskiftning.

I version 10 blev den erstattet takket være heroisk indsats af Amit Langote med moderne "deklarativ opdeling". Denne nye teknologi betød, at du ikke længere behøvede at skrive kode manuelt for at dirigere tuples til deres korrekte partitioner, og ikke længere behøvede manuelt at erklære korrekte begrænsninger for hver partition:Systemet gjorde disse ting automatisk for dig.

Desværre er det i PostgreSQL 10 stort set alt, hvad det gjorde. På grund af den store kompleksitet og tidsbegrænsningerne var der mange ting i PostgreSQL 10-implementeringen, der manglede. Robert Haas holdt et foredrag om det i Warszawas PGConf.EU.

Mange mennesker arbejdede på at forbedre situationen for PostgreSQL 11; her er mit forsøg på en gentælling. Jeg opdeler disse i tre områder:

  1. Nye partitioneringsfunktioner
  2. Bedre DDL-understøttelse af partitionerede tabeller
  3. Ydeevneoptimeringer.

Nye partitioneringsfunktioner

I PostgreSQL 10 kan dine partitionerede tabeller være det i RANGE og LISTE tilstande. Disse er kraftfulde værktøjer til at basere mange databaser i den virkelige verden på, men for mange andre designs har du brug for den nye tilstand tilføjet i PostgreSQL 11:HASH partitionering . Mange kunder har brug for dette, og Amul Sul arbejdet hårdt for at gøre det muligt. Her er et simpelt eksempel:

CREATE TABLE clients (
client_id INTEGER, name TEXT
) PARTITION BY HASH (client_id);

CREATE TABLE clients_0 PARTITION OF clients
    FOR VALUES WITH (MODULUS 3, REMAINDER 0);
CREATE TABLE clients_1 PARTITION OF clients
    FOR VALUES WITH (MODULUS 3, REMAINDER 1);
CREATE TABLE clients_2 PARTITION OF clients
    FOR VALUES WITH (MODULUS 3, REMAINDER 2);

Det er ikke obligatorisk at bruge den samme modulværdi for alle partitioner; dette lader dig oprette flere partitioner senere og omfordele rækkerne én partition ad gangen, hvis det er nødvendigt.

En anden meget nyttig funktion, skrevet af Amit Khandekar er evnen til at tillade OPDATERING at flytte rækker fra en partition til en anden - det vil sige, hvis der er en ændring i værdierne af partitioneringskolonnen, flyttes rækken automatisk til den korrekte partition. Tidligere ville den operation have givet en fejl.

Endnu en ny funktion, skrevet af Amit Langote og med venlig hilsen , er det INSERT ON CONFLICT UPDATE kan anvendes på partitionerede tabeller . Tidligere ville denne kommando mislykkes, hvis den var målrettet mod en opdelt tabel. Du kan få det til at fungere ved at vide præcis, hvilken partition rækken ville ende i, men det er ikke særlig praktisk. Jeg vil ikke gennemgå detaljerne i den kommando, men hvis du nogensinde har ønsket, at du havde UPSERT i Postgres, dette er det. En advarsel er, at OPDATERING handling flytter muligvis ikke rækken til en anden partition.

Endelig endnu en sød ny funktion i PostgreSQL 11, denne gang af Jeevan Ladhe, Beena Emerson, Ashutosh Bapat, Rahila Syed, og Robert Haas er understøttelse af en standardpartition i en inddelt tabel , det vil sige en partition, der modtager alle rækker, der ikke passer ind i nogen af ​​de almindelige partitioner. Men selvom den er god på papiret, er denne funktion ikke særlig praktisk i produktionsindstillinger, fordi nogle operationer kræver tungere låsning med standardpartitioner end uden. Eksempel:oprettelse af en ny partition kræver scanning af standardpartitionen for at fastslå, at ingen eksisterende rækker matcher den nye partitions grænser. Måske vil disse låsekrav blive sænket i fremtiden, men i mellemtiden er mit forslag ikke at bruge det.

Bedre DDL-understøttelse

I PostgreSQL 10 ville visse DDL nægte at arbejde, når de blev anvendt på en partitioneret tabel, og krævede, at du behandler hver partition individuelt. I PostgreSQL 11 har vi rettet et par af disse begrænsninger, som tidligere annonceret af Simon Riggs. For det første kan du nu bruge CREATE INDEX på et opdelt bord , en funktion skrevet af yours truly. Denne kan ses som blot et spørgsmål om at reducere kedsomhed:i stedet for at gentage kommandoen for hver partition (og sørge for aldrig at glemme for hver ny partition), kan du kun gøre det én gang for den overordnede partitionerede tabel, og den gælder automatisk til alle partitioner, eksisterende og fremtidige.

En cool ting at huske på er matchningen af ​​eksisterende indekser i partitioner. Som du ved, er oprettelse af et indeks et blokerende forslag, så jo mindre tid det tager, jo bedre. Jeg skrev denne funktion, så eksisterende indekser i partitionen ville blive sammenlignet med de indekser, der oprettes, og hvis der er matches, er det ikke nødvendigt at scanne partitionen for at oprette nye indekser:de eksisterende indekser ville blive brugt.

Sammen med dette kan du også oprette UNIQUE begrænsninger, samt PRIMÆR NØGLE begrænsninger . To forbehold:For det første skal partitionsnøglen være en del af den primære nøgle. Dette gør det muligt at udføre de unikke kontroller lokalt pr. partition, hvorved globale indekser undgås. For det andet er det ikke muligt at have fremmednøgler, der refererer til disse primære nøgler endnu. Jeg arbejder på det til PostgreSQL 12.

En anden ting du kan gøre (takket være den samme person) er oprette FOR HVER RÆKKE udløses på en opdelt tabel , og få det til at gælde for alle partitioner (eksisterende og fremtidige). Som en bivirkning kan du have udskudt unik begrænsninger på opdelte tabeller. En advarsel:kun EFTER triggere er tilladt, indtil vi finder ud af, hvordan vi skal håndtere FØR triggere, der flytter rækker til en anden partition.

Endelig kan en partitioneret tabel have FOREIGN KEY begrænsninger . Dette er meget praktisk til at opdele store faktatabeller og samtidig undgå dinglende referencer, som alle hader. Min kollega Gabriele Bartolini tog mig i skødet, da han fandt ud af, at jeg havde skrevet og begået dette, og råbte, at dette var en game-changer, og hvordan kunne jeg være så ufølsom, at jeg ikke skulle informere ham om dette. Mig, jeg fortsætter bare med at hacke koden for sjov.

Ydeevnearbejde

Tidligere var forbehandling af forespørgsler for at finde ud af, hvilke partitioner der ikke skulle scannes (begrænsningsekskludering), ret forenklet og langsom. Dette er blevet forbedret af beundringsværdigt teamwork udført af Amit Langote, David Rowley, Beena Emerson, Dilip Kumar for at introducere "hurtigere beskæring" først og "runtime pruning" baseret på det bagefter. Resultatet er meget mere kraftfuldt og hurtigere (David Rowley har allerede beskrevet dette i en tidligere artikel.) Efter al denne indsats anvendes partitionsbeskæring på tre punkter i en forespørgsels levetid:

  1. På tidspunktet for forespørgselsplanen,
  2. Når forespørgselsparametrene modtages,
  3. På hvert punkt, hvor en forespørgselsnode sender værdier som parametre til en anden node.

Dette er en bemærkelsesværdig forbedring i forhold til det oprindelige system, som kun kunne anvendes ved forespørgselsplanen, og jeg tror, ​​det vil glæde mange.

Du kan se denne funktion i aktion ved at sammenligne EXPLAIN output for en forespørgsel før og efter deaktivering af enable_partition_pruning mulighed. Som et meget forenklet eksempel kan du sammenligne denne plan uden beskæring:

SET enable_partition_pruning TO off;
EXPLAIN (ANALYZE, COSTS off)
SELECT * FROM clientes
WHERE cliente_id = 1234;
                                QUERY PLAN                                
-------------------------------------------------------------------------
 Append (actual time=6.658..10.549 rows=1 loops=1)
   ->  Seq Scan on clientes_1 (actual time=4.724..4.724 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 24978
   ->  Seq Scan on clientes_00 (actual time=1.914..1.914 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12644
   ->  Seq Scan on clientes_2 (actual time=0.017..1.021 rows=1 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12570
   ->  Seq Scan on clientes_3 (actual time=0.746..0.746 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12448
   ->  Seq Scan on clientes_01 (actual time=0.648..0.648 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12482
   ->  Seq Scan on clientes_4 (actual time=0.774..0.774 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12400
   ->  Seq Scan on clientes_5 (actual time=0.717..0.717 rows=0 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12477
 Planning Time: 0.375 ms
 Execution Time: 10.603 ms

med den, der er fremstillet med beskæring:

EXPLAIN (ANALYZE, COSTS off)
SELECT * FROM clientes
WHERE cliente_id = 1234;
                                QUERY PLAN                               
----------------------------------------------------------------------
 Append (actual time=0.054..2.787 rows=1 loops=1)
   ->  Seq Scan on clientes_2 (actual time=0.052..2.785 rows=1 loops=1)
         Filter: (cliente_id = 1234)
         Rows Removed by Filter: 12570
 Planning Time: 0.292 ms
 Execution Time: 2.822 ms

Jeg er sikker på, at du vil finde det overbevisende. Du kan se et væld af mere sofistikerede eksempler ved at læse den forventede fil med regressionstests.

Et andet punkt var introduktionen af ​​partitionsvise sammenføjninger af Ashutosh Bapat . Ideen her er, at hvis du har to opdelte tabeller, og de er opdelt på identiske måder, så kan du, når de er forbundet, forbinde hver partition på den ene side til dens matchende partition på den anden side; dette er meget bedre end at forbinde hver partition på siden til hver partition på den anden side. Det faktum, at partitionsskemaerne skal matche præcist kan få dette til at virke usandsynligt at have meget brug i den virkelige verden, men i virkeligheden er der mange situationer, hvor dette gælder. Eksempel:en ordretabel og dens tilsvarende tabel orders_items. Heldigvis er der allerede masser af arbejde med at lempe denne begrænsning.

Det sidste punkt, jeg vil nævne, er partitionsvise aggregater af Jeevan Chalke, Ashutosh Bapat, og Robert Haas . Denne optimering betyder, at en aggregering, der inkluderer partitionsnøglerne i GROUP BY klausul kan udføres ved at aggregere hver partitions rækker separat, hvilket er meget hurtigere.

Afsluttende tanker

Efter den betydelige udvikling i denne cyklus har PostgreSQL en meget mere overbevisende partitioneringshistorie. Selvom der stadig er mange forbedringer, der skal foretages, især for at forbedre ydeevnen og samtidigheden af ​​forskellige operationer, der involverer partitionerede tabeller, er vi nu på et punkt, hvor deklarativ partitionering er blevet et meget værdifuldt værktøj til at tjene mange use cases. Hos 2ndQuadrant vil vi fortsætte med at bidrage med kode for at forbedre PostgreSQL på dette område og andre, ligesom vi har gjort for hver enkelt udgivelse siden 8.0.


  1. Er der en officiel anbefaling fra Oracle om brugen af ​​eksplicitte ANSI JOINs vs implicitte joinforbindelser?

  2. SQL Server:Isolationsniveau lækker på tværs af poolede forbindelser

  3. Udarbejdet erklæring om Postgresql in Rails

  4. Sådan fungerer OCTET_LENGTH() i MariaDB