Partitionering
at dele én tabel i fysiske stykker
Hvad det er
Partitionering, også kaldet tabelpartitionering, gemmer rækkerne fra én logisk tabel i flere almindelige tabeller, der hver rummer de rækker, hvis nøgle falder inden for dens egne grænser, mens databaseforespørgsler og skrivninger fortsat bruger det ene navn, de altid har brugt. PostgreSQL, som denne side følger, kalder sin indbyggede form deklarativ partitionering, på engelsk declarative partitioning: tabellen erklæres som partitioneret med en metode og en partitionsnøgle, og hver partition oprettes som en tabel for sig.
Den partitionerede tabel rummer selv ingen rækker. En indsættelse sendes videre til den partition, hvis grænser passer til nøglen, og en opdatering, der ændrer nøglen, flytter rækken til den partition, hvor den nu hører hjemme. Alle partitioner har forældretabellens kolonner, men hver kan have sine egne indeks, lagringsindstillinger og sit eget tablespace, og en partition kan selv partitioneres igen.
Der er tre metoder. Range-partitionering giver hver partition et interval af værdier, oftest datoer; den nedre grænse er med, og den øvre er ikke, så med intervallerne 1–10 og 10–20 hører værdien 10 til det andet. List-partitionering opregner de værdier, hver partition tager imod, for eksempel en gruppe landekoder. Hash-partitionering giver hver partition et modulus og en rest, og en række havner der, hvor hashen af dens nøgle giver netop den rest, hvilket fordeler rækkerne jævnt, når der ikke er et naturligt interval at skære efter. En standardpartition, på engelsk default partition, kan fange de værdier, der ikke passer i nogen anden, ved range og list, men ikke ved hash.
Partitionering foregår inden for én databaseserver. At fordele data over separate servere, hver med sin egen kopi af skemaet, er sharding, og det er et langt større skridt. SQL Server og BigQuery har deres egne former for partitionering med deres egen syntaks, og idéerne nedenfor gælder også der, selv hvor kommandoerne er andre.
Hvorfor det betyder noget
De fleste store tabeller vokser i den ene ende. Ordrer, målinger, logs og hændelser kommer ind hver dag, bliver liggende i måneder eller år, og de fleste forespørgsler spørger til den nyeste del. Uden partitionering arbejder hver forespørgsel mod én tabel og ét sæt indeks, der bliver ved med at vokse.
Med en partitionsnøgle, der passer til forespørgslerne, kan planneren bevise, at de fleste partitioner umuligt kan rumme en matchende række, og lade dem helt ude af planen. PostgreSQL kalder det partition pruning. En rapport over sidste uge læser sidste uges partition og intet andet. Dokumentationen nævner også en mindre oplagt gevinst: fordi partitionerne overtager rollen fra de øverste niveauer i et indekstræ, er der større chance for, at de mest brugte dele af indeksene kan ligge i hukommelsen.
Mest ændrer det sig, når gamle data skal væk. At slette en måneds rækker fra én
samlet tabel er en lang DELETE, der efterlader døde rækker, som VACUUM skal
rydde op efter. Med én partition pr. måned er den samme oprydning én
DROP TABLE eller én DETACH PARTITION, som dokumentationen kalder langt
hurtigere end en masseoperation, og som helt undgår arbejdet med VACUUM. En
frakoblet partition er stadig en almindelig tabel, så den kan sikkerhedskopieres
eller opsummeres, før den slettes, og partitioner, der sjældent læses, kan ligge
på billigere og langsommere lager.
Gevinsten har en pris, og dokumentationen siger ligeud, hvornår den betaler sig. Hver partition giver planlægningen mere arbejde, og fordelene er normalt kun umagen værd, når tabellen ellers ville være meget stor; tommelfingerreglen er, at tabellen bør være større end databaseserverens fysiske hukommelse. Én ting rører partitionering heller ikke ved: en enkelt række, som mange transaktioner kæmper om, er stadig én række i én partition bag én lås.
Sådan ser det ud i praksis
En note om at skalere en database, før den shardes, har et afsnit om at
partitionere tabellerne omkring den varme række.
Dens orders-tabel er partitioneret pr. måned på created_at, med primærnøglen
(id, created_at), fordi nøglen skal indeholde partitionskolonnen. En
forespørgsel, der nævner et datointerval, læser kun partitionerne inden for det,
denne måneds indekser forbliver små nok til at ligge i hukommelsen, og oprydning
bliver én DETACH PARTITION i stedet for en DELETE af millioner af rækker.
Noten siger også ligeud, hvor grænsen går: partitionering gør ikke den omstridte
række hurtigere, og en forespørgsel, der ikke nævner nøglen, læser hver eneste
partition.
Et PostgreSQL-projekt for en applikation med brugerkonti, tenant-oplysninger, geografiske data og dagligt elforbrug delte eltabellen op i tidsintervaller pr. dag og måned og brugte hash-partitionering til de geografiske data. Det skete sammen med GiST- og composite-indeks og omskrevne forespørgsler, og det samlede resultat var svartider, der blev 10 gange bedre, og et ressourceforbrug, der faldt med 40 %.
Partitionering kan også bruges i flere databasemotorer side om side. Med over 100 databaser på tværs af PostgreSQL, SQL Server og BigQuery, primært med GIS-data og tidsseriedata, indgik tidsseriepartitionering i skemadesignet til PostgreSQL og SQL Server, mens BigQuery brugte tidspartitionerede og clustrede tabeller. Sammen med spatial indeksering, materialized views og caching faldt svartiderne for de data med 40–60 %.
Hvor det går galt
Partitionering flytter noget af arbejdet fra forespørgselstid til designtid, og de fleste problemer kommer af en designbeslutning, der blev truffet uden forespørgslerne for øje.
Indsættelser begynder at fejle den første dag i en ny måned. Ingen partition dækker det nye interval, og PostgreSQL afviser en indsættelse i forældretabellen, der ikke passer til nogen eksisterende partition. Opret partitionerne i god tid fra et planlagt job, der genererer DDL’en, som dokumentationen foreslår, og send en alarm, når den nyeste partition nærmer sig sin slutning:
CREATE TABLE orders_2026_11
PARTITION OF orders
FOR VALUES FROM ('2026-11-01')
TO ('2026-12-01');
En standardpartition stopper fejlene, men så havner rækkerne for alle udækkede
måneder sammen i den ene tabel, og når en ny partition senere tilkobles, scannes
standardpartitionen under en ACCESS EXCLUSIVE-lås, medmindre en
CHECK-constraint på standardpartitionen allerede udelukker det nye interval.
Behandl en standardpartition med rækker i som en alarm.
Forespørgsler er langsommere efter partitioneringen end før. De almindelige
forespørgsler filtrerer ikke på partitionsnøglen, så hver af dem læser alle
partitioner og deres indeks. En anden årsag er antallet af partitioner:
dokumentationen siger, at planneren klarer op til et par tusind ret godt,
forudsat at typiske forespørgsler skærer alle undtagen nogle få fra, og at
planlægningstid og hukommelsesforbrug vokser derudover. Vælg nøglen ud fra de
WHERE-betingelser, programmet allerede skriver, tjek med EXPLAIN, at kun de
forventede partitioner optræder, og hold antallet i et rimeligt forhold. Én
partition pr. kunde er en typisk fælde, fordi antallet vokser med forretningen;
dokumentationen foreslår i stedet hash-partitionering med et rimeligt antal
partitioner.
En primærnøgle eller en unik constraint lader sig ikke oprette. På en
partitioneret tabel skal en unik nøgle eller primærnøgle indeholde alle
partitionsnøglens kolonner, og partitionsnøglen må ikke indeholde udtryk eller
funktionskald. Hver partitions indeks kan kun håndhæve unikhed inden for sin
egen partition. Tilføj nøglekolonnen til constrainten, som notens orders-tabel
gør med (id, created_at).
En indeksbygning på forældretabellen blokerer skrivninger længe. Et
almindeligt CREATE INDEX på forældretabellen spærrer for skrivninger, indtil
hver partition er indekseret, og CONCURRENTLY virker pr. partition og ikke på
forældretabellen. Opret indekset med ON ONLY på forældretabellen, så det
markeres som ugyldigt, byg indekset på hver partition med CONCURRENTLY, og
tilkobl hvert af dem med ALTER INDEX ... ATTACH PARTITION. Når alle
partitionernes indeks er tilkoblet, bliver forældreindekset gyldigt af sig selv.
En eksisterende stor tabel lader sig ikke lægge om på stedet. PostgreSQL gør
ikke en almindelig tabel til en partitioneret. Opret den nye partitionerede
tabel, og tilkobl den gamle tabel som en af dens partitioner. Tilføj før
tilkoblingen en CHECK-constraint, der svarer til partitionens grænser, så
tilkoblingen ikke scanner hele tabellen, mens den holder en
ACCESS EXCLUSIVE-lås på den.
Læs mere
- Table Partitioning — PostgreSQL-kapitlet: de tre metoder, pruning, vedligeholdelse, begrænsninger og best practices.
- CREATE TABLE
—
PARTITION BY,PARTITION OF, partitionsgrænser og standardpartitionen. - ALTER TABLE
—
ATTACH PARTITIONogDETACH PARTITION, og hvad hver af dem låser. - CREATE INDEX
— indeks på partitionerede tabeller,
ON ONLYog bygning medCONCURRENTLY.