Wat is partitioneren?
Partitioneren splitst grote tabellen in kleinere stukken, wat helpt bij het verbeteren van de queryprestaties, het vereenvoudigen van onderhoudstaken, het verbeteren van de efficiëntie van gegevensarchivering en snellere databaseback-ups. U kunt meer lezen over PostgreSQL-partitionering in onze blog "A Guide to Partitioning Data In PostgreSQL".
Met de recente release van PostgreSQL 11 zijn er veel nieuwe verbazingwekkende partitioneringsfuncties. De details van deze nieuwe partitioneringsfuncties worden in deze blog behandeld met enkele codevoorbeelden.
De partitietoetsen bijwerken
Vóór PostgreSQL 11 was de update-instructie die de waarde van de partitiesleutel wijzigt, beperkt en niet toegestaan. In de nieuwe versie is dit nu mogelijk. Update-instructie kan de waarde van de partitiesleutel wijzigen; het verplaatst de rijen in feite naar de juiste partitietabel. Onder de motorkap voert het in feite DELETE FROM oude partitie uit en INSERT in nieuwe partitie ( DELETE + INSERT).
Oké, laten we dit eens testen. Maak een tabel en controleer hoe de update werkt op de partitiesleutel.
CREATE TABLE customers(cust_id bigint NOT NULL,cust_name varchar(32) NOT NULL,cust_address text,
cust_country text)PARTITION BY LIST(cust_country);
CREATE TABLE customer_ind PARTITION OF customers FOR VALUES IN ('ind');
CREATE TABLE customer_jap PARTITION OF customers FOR VALUES IN ('jap');
CREATE TABLE customers_def PARTITION OF customers DEFAULT;
severalnines_v11=# INSERT INTO customers VALUES (2039,'Puja','Hyderabad','ind');
INSERT 0 1
severalnines_v11=# SELECT * FROM customer_ind;
cust_id | cust_name | cust_address | cust_country
2039 | Puja | Hyderabad | ind
(1 row)
severalnines_v11=# UPDATE customers SET cust_country ='jap' WHERE cust_id=2039;
UPDATE 1
-- it moved the row to correct partition table.
severalnines_v11=# SELECT * FROM customer_ind;
cust_id | cust_name | cust_address | cust_country
---------+-----------+--------------+--------------
(0 rows)
severalnines_v11=# SELECT * FROM customer_jap;
cust_id | cust_name | cust_address | cust_country
---------+-----------+--------------+--------------
2039 | Puja | Hyderabad | jap
(1 row)
Let op:De UPDATE geeft een foutmelding als er geen standaard partitietabel is en de bijgewerkte waarden niet overeenkomen met partitiecriteria in een onderliggende tabel.
severalnines_v11=# UPDATE customers1 SET cust_country ='ypp' WHERE cust_id=2039;
2018-11-21 00:13:54.901 IST [1479] ERROR: no partition of relation "customers1" found for row
2018-11-21 00:13:54.901 IST [1479] DETAIL: Partition key of the failing row contains (cust_country) = (ypp).
2018-11-21 00:13:54.901 IST [1479] STATEMENT: UPDATE customers1 SET cust_country ='ypp' WHERE cust_id=2039;
ERROR: no partition of relation "customers1" found for row
DETAIL: Partition key of the failing row contains (cust_country) = (ypp).
[ -- the value of cust_country was not mapped to any part table so it failed]
Een standaardpartitie maken
De PostgreSQL 11 DEFAULT-partitiefunctie slaat tuples op die niet aan een andere partitie zijn toegewezen. Vóór PostgreSQL 11 zouden deze rijen fouten bevatten. Een rij die niet aan een partitietabel is toegewezen, wordt in de standaardpartitie ingevoegd.
Lab Voorbeeld:`VS` landcode is niet gedefinieerd in de partitietabel hieronder, maar wordt toch met succes in de standaardtabel ingevoegd.
CREATE TABLE customers_def PARTITION OF customers DEFAULT;
severalnines_v11=# INSERT INTO customers VALUES (4499,'Tony','Arizona','USA');
INSERT 0 1
severalnines_v11=# select * FROM customers_def;
cust_id | cust_name | cust_address | cust_country
---------+-----------+--------------+--------------
4499 | Tony | Arizona | USA
Waarschuwing:de standaardpartitie voorkomt dat er nieuwe partities worden toegevoegd als die partitiewaarde in de standaardtabel voorkomt. In dit geval bestond `USA` in de standaardpartitie, dus het zal niet werken zoals hieronder.
severalnines_v11=# CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');
2018-11-21 00:46:34.890 IST [1526] ERROR: updated partition constraint for default partition "customers_def" would be violated by some row
2018-11-21 00:46:34.890 IST [1526] STATEMENT: CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');ERROR: updated partition constraint for default partition "customers_def" would be violated by some row
severalnines_v11=#
Resolution - You need to move/remove those rows from Default table, then it will then let you create new part table like below.
severalnines_v11=# DELETE FROM customers_def WHERE cust_country in ('USA'); DELETE 1
severalnines_v11=# CREATE TABLE customer_usa PARTITION OF customers FOR VALUES IN ('USA');
CREATE TABLE
severalnines_v11=#
Nudgets :
DEFAULT-partitie kan niet worden opgegeven voor de HASH-gepartitioneerde tabel. Er kan niet meer dan één DEFAULT-tabel voor de partitietabel zijn.
Hash-partitionering
Het is een nieuw partitiemechanisme, als je niet kunt beslissen over een bereik- of lijstpartitie (omdat je niet zeker weet hoe groot de bucket zou zijn). Hash-partitionering lost dit probleem met gegevensdistributie op.
De tabel is gepartitioneerd door een modulus en een rest voor elke partitie op te geven. Elke partitie bevat de rijen waarvoor de hash-waarde van de partitiesleutel gedeeld door de opgegeven modulus de opgegeven rest oplevert. De HASH-functie zorgt ervoor dat rijen grotendeels gelijkmatig worden verdeeld in de hele partitietabel.
Om te beginnen moet u beslissen hoeveel nummers van de partitietabel nodig zijn en dienovereenkomstig kunnen modulus en rest worden gedefinieerd; als de modulus 4 zou zijn, kan de rest alleen van [0-3] zijn.
[Modulus - Aantal tafels | Rest - Welke waarde van de rest gaat naar welke emmer ]
Een hashpartitie instellen
-- hash partition
CREATE TABLE part_hash_test (x int, y text) PARTITION BY hash (x);
-- create child partitions
CREATE TABLE part_hash_test_0 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE part_hash_test_1 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE part_hash_test_2 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE part_hash_test_3 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 3);
Voeg 50k records in de bovenliggende tabel in:
severalnines_v11=# INSERT INTO part_hash_test SELECT generate_series(0,50000);
INSERT 0 50001
en kijk hoe het records gelijkmatig verdeelde in de onderliggende tabel ...
severalnines_v11=# SELECT count(1),tableoid::regclass FROM part_hash_test GROUP by 2 order by 2 ;
count | tableoid
-------+------------------
12537 | part_hash_test_0
12473 | part_hash_test_1
12509 | part_hash_test_2
12482 | part_hash_test_3
(4 rows)
We kunnen het aantal partities dat eerder door `Modulus` is gespecificeerd niet wijzigen, dus u moet ruim voor de vereisten voor het aantal partitietabellen plannen.
Er zal een fout optreden wanneer u een nieuwe partitie probeert toe te voegen met een andere rest.
severalnines_v11=# CREATE TABLE part_hash_test_5 PARTITION OF part_hash_test FOR VALUES
WITH (MODULUS 4, REMAINDER 5);severalnines_v11-#
2018-11-21 01:51:28.966 IST [1675] ERROR: remainder for hash partition must be less than modulus
2018-11-21 01:51:28.966 IST [1675] STATEMENT: CREATE TABLE part_hash_test_5 PARTITION OF part_hash_test FOR VALUES WITH (MODULUS 4, REMAINDER 5);
Hash-partitionering kan op elk gegevenstype werken en het kan ook voor het UUID-type werken. Het wordt altijd aanbevolen dat het aantal tabellen een macht van 2 is, en het is ook niet verplicht om dezelfde modulus te gebruiken bij het maken van de tabel; dit zal helpen om de partitietabel later te maken als dat nodig is.
Deze implementatie zou ook het vacuüm sneller maken en partitiegewijze join mogelijk maken.
Ondersteuning voor buitenlandse sleutels
Vóór PostgreSQL 11 werd de externe sleutel in de partitietabel niet ondersteund. De externe sleutels zijn nu mogelijk in de partitietabel en hieronder is hoe...
severalnines_v11=# CREATE TABLE customers2 ( cust_id integer PRIMARY KEY );
CREATE TABLE
severalnines_v11=# CREATE TABLE account (
ac_date date NOT NULL,
cust_id integer REFERENCES customers2(cust_id),
amount INTEGER NOT NULL) PARTITION BY RANGE (ac_date);
CREATE TABLE
Automatisch indexeren op onderliggende tabellen
In eerdere versies van PostgreSQL was het een handmatige inspanning om op elke partitietabel een index te maken. In PostgreSQL versie 11 is het best handig voor gebruikers. Zodra de index is gemaakt op de hoofdtabel, zal deze automatisch de index maken met dezelfde configuratie op alle bestaande onderliggende partities en ook zorgen voor toekomstige partitietabellen.
Index gemaakt op hoofdtabel
severalnines_v11=# CREATE index idx_name ON customers(cust_name);
CREATE INDEX
Het creëerde automatisch de index op alle onderliggende tabellen, zoals hieronder. (Controleer met catalogustabel)
severalnines_v11=# SELECT tablename,indexname,indexdef FROM pg_indexes WHERE tablename ilike '%customer_%';
tablename | indexname | indexdef
---------------+-----------------------------+------------------------------------------------------------------------------------------
customer_ind | customer_ind_cust_name_idx | CREATE INDEX customer_ind_cust_name_idx ON public.customer_ind USING btree (cust_name)
customer_jap | customer_jap_cust_name_idx | CREATE INDEX customer_jap_cust_name_idx ON public.customer_jap USING btree (cust_name)
customer_usa | customer_usa_cust_name_idx | CREATE INDEX customer_usa_cust_name_idx ON public.customer_usa USING btree (cust_name)
customers_def | customers_def_cust_name_idx | CREATE INDEX customers_def_cust_name_idx ON public.customers_def USING btree (cust_name)
(4 rows)
Index kan alleen worden gemaakt op een hoofdtabel, het kan niet op een onderliggende tabel. Automatisch gegenereerde indexen kunnen niet afzonderlijk worden verwijderd.
Automatische activering van onderliggende tabellen
Zodra de trigger is gemaakt op de hoofdtabel, wordt de trigger automatisch gemaakt voor alle onderliggende tabellen (dit gedrag is vergelijkbaar met het gedrag dat wordt gezien voor index).
In staat om een unieke index te creëren
In versie 11 kunnen unieke indexen aan de hoofdtabel worden toegevoegd, waardoor de unieke beperking op alle bestaande onderliggende tabellen en toekomstige partitietabellen ontstaat.
Laten we een hoofdtabel maken met unieke beperkingen.
CREATE TABLE uniq_customers( cust_id bigint NOT NULL, cust_name varchar(32) NOT NULL, cust_address text, cust_country text,cust_email text, unique(cust_email,cust_id,cust_country) )PARTITION BY LIST(cust_country);
De unieke beperking is automatisch gemaakt voor de onderliggende tabel, zoals hieronder.
severalnines_v11=# SELECT table_name,constraint_name,constraint_type FROM information_schema.table_constraints WHERE table_name ilike '%uniq%' AND constraint_type = 'UNIQUE';
table_name | constraint_name | constraint_type
-------------------+-------------------------------------------------------+-----------------
uniq_customers | uniq_customers_cust_email_cust_id_cust_country_key | UNIQUE
uniq_customer_ind | uniq_customer_ind_cust_email_cust_id_cust_country_key | UNIQUE
(2 rows)
Let op:een unieke beperking op de bovenliggende tabel garandeert niet echt uniekheid in de hele partitiehiërarchie. Het is geen globale beperking, het is alleen lokaal.
Download de whitepaper vandaag PostgreSQL-beheer en -automatisering met ClusterControlLees wat u moet weten om PostgreSQL te implementeren, bewaken, beheren en schalenDownload de whitepaperSnellere queryprestaties
Dynamisch snoeien van partities
In PostgreSQL 11 maakt de binaire zoekactie een snellere identificatie mogelijk van de vereiste onderliggende tabellen, of deze nu LIST of RANGE zijn gepartitioneerd. De hash-functie vindt de overeenkomende partitie voor de HASH-partitie. Het elimineert in feite dynamisch de partitietabel(len) die niet nodig zijn en verhoogt de prestatie van de query.
Het dynamisch opschonen van partities kan worden bestuurd door de parameter `enable_partition_pruning`.
severalnines_v11=# show enable_partition_pruning;
enable_partition_pruning
--------------------------
off
(1 row)
severalnines_v11=# EXPLAIN SELECT * from customers where cust_country = 'ind';
QUERY PLAN
---------------------------------------------------------------------
Append (cost=0.00..18.54 rows=5 width=154)
-> Seq Scan on customer_ind (cost=0.00..1.01 rows=1 width=154)
Filter: (cust_country = 'ind'::text)
-> Seq Scan on customer_jap (cost=0.00..1.00 rows=1 width=154)
Filter: (cust_country = 'ind'::text)
-> Seq Scan on customer_usa (cost=0.00..15.50 rows=2 width=154)
Filter: (cust_country = 'ind'::text)
-> Seq Scan on customers_def (cost=0.00..1.00 rows=1 width=154)
Filter: (cust_country = 'ind'::text)
(9 rows)
Enabled the parameter to ON.
severalnines_v11=# set enable_partition_pruning TO on;
SET
severalnines_v11=# EXPLAIN SELECT * from customers where cust_country = 'ind';
QUERY PLAN
--------------------------------------------------------------------
Append (cost=0.00..1.02 rows=1 width=154)
-> Seq Scan on customer_ind (cost=0.00..1.01 rows=1 width=154)
Filter: (cust_country = 'ind'::text)
(3 rows)
De andere geweldige implementatie is als volgt.
Execution-Time Partition Snoeien
In PostgreSQL-versies ouder dan 11 kan het opschonen van partities alleen plaatsvinden tijdens de geplande planning; planner vereist een waarde van partitiesleutel om de juiste partitie te identificeren. Dit gedrag is opgelost in PostgreSQL 11, omdat de uitvoeringstijdplanner zou weten welke waarde wordt geleverd en op basis daarvan partitieselectie / eliminatie mogelijk is en veel sneller zou werken. De use case kan een query zijn die gebruikmaakt van een parameter (voorbereide instructie) OF een subquery die de waarde als parameter levert.
Example : cus_country is partition key and getting value from subquery
severalnines_v11=# explain analyze select * from customers WHERE cust_country = (select cust_count_x FROM test_execution_prun1);
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Append (cost=23.60..42.14 rows=5 width=154) (actual time=0.019..0.020 rows=0 loops=1)
InitPlan 1 (returns $0)
-> Seq Scan on test_execution_prun1 (cost=0.00..23.60 rows=1360 width=32) (actual time=0.006..0.007 rows=1 loops=1)
-> Seq Scan on customer_ind (cost=0.00..1.01 rows=1 width=154) (never executed)
Filter: (cust_country = $0)
-> Seq Scan on customer_jap (cost=0.00..1.00 rows=1 width=154) (never executed)
Filter: (cust_country = $0)
-> Seq Scan on customer_usa (cost=0.00..15.50 rows=2 width=154) (never executed)
Filter: (cust_country = $0)
-> Seq Scan on customers_def (cost=0.00..1.00 rows=1 width=154) (actual time=0.003..0.003 rows=0 loops=1)
Filter: (cust_country = $0)
Planning Time: 0.237 ms
Execution Time: 0.057 ms
(13 rows)
In het bovenstaande plan uitleggen, kunnen we zien dat de planner op het moment van uitvoering ter plekke de juiste partitietabel identificeerde op basis van parameterwaarde, en veel sneller liep en geen tijd besteedde aan scan/loop op andere partitietabel (zie nooit uitgevoerde gedeelte in plan uitleggen hierboven). Dit is zeer krachtig en begon een nieuw tijdperk van prestatieverbetering in partitionering.
Partition Wise Aggregate
Parameter:enable_partitionwise_aggregate
Als de partitiesleutel overeenkomt met de groeperingssleutel, zal elke partitie een discrete set groepen produceren in plaats van de hele partitie in één keer te scannen. Het zal de parallelle aggregatie doen voor elke partitie en tijdens het uiteindelijke resultaat voegt het alle resultaten samen.
severalnines_v11=# explain SELECT count(1),cust_country FROM customers GROUP BY 2;
QUERY PLAN
----------------------------------------------------------------------------
HashAggregate (cost=21.84..23.84 rows=200 width=40)
Group Key: customer_ind.cust_country
-> Append (cost=0.00..19.62 rows=443 width=32)
-> Seq Scan on customer_ind (cost=0.00..1.01 rows=1 width=32)
-> Seq Scan on customer_jap (cost=0.00..1.00 rows=1 width=32)
-> Seq Scan on customer_usa (cost=0.00..14.40 rows=440 width=32)
-> Seq Scan on customers_def (cost=0.00..1.00 rows=1 width=32)
(7 rows)
severalnines_v11=# SET enable_partitionwise_aggregate TO on;
SET
severalnines_v11=# explain SELECT count(1),cust_country FROM customers GROUP BY 2;
QUERY PLAN
----------------------------------------------------------------------------
Append (cost=1.01..22.67 rows=203 width=40)
-> HashAggregate (cost=1.01..1.02 rows=1 width=40)
Group Key: customer_ind.cust_country
-> Seq Scan on customer_ind (cost=0.00..1.01 rows=1 width=32)
-> HashAggregate (cost=1.00..1.01 rows=1 width=40)
Group Key: customer_jap.cust_country
-> Seq Scan on customer_jap (cost=0.00..1.00 rows=1 width=32)
-> HashAggregate (cost=16.60..18.60 rows=200 width=40)
Group Key: customer_usa.cust_country
-> Seq Scan on customer_usa (cost=0.00..14.40 rows=440 width=32)
-> HashAggregate (cost=1.00..1.01 rows=1 width=40)
Group Key: customers_def.cust_country
-> Seq Scan on customers_def (cost=0.00..1.00 rows=1 width=32)
(13 rows)
Dit is zeker sneller omdat het parallelle aggregatieverwerking en scannen per partitie omvat.
Catalogusquery kan worden gebruikt om alle bovenliggende partitietabellen te kennen.
SELECT relname FROM pg_class WHERE oid in (select partrelid FROM pg_partitioned_table);
Korte partitiefunctiematrix
Functies voor partitioneren | v11 | v10 |
---|---|---|
Standaardpartitie | JA | NEE |
Overerving van buitenlandse tafels | JA | NEE |
Partitionering op hashsleutel | JA | NEE |
Ondersteuning voor PK &FK | JA | NEE |
UPDATE op een partitiesleutel | JA | NEE |
Geautomatiseerde Inexes op CT | JA | NEE |
Geautomatiseerde triggers op CT | JA | NEE |
Uitvoeringstijd partitie snoeien | JA | NEE |
Partitie verstandig deelnemen | JA | NEE |
Dynamische partitie snoeien | JA | NEE |
Wat nu?
Partitioneringsprestaties
Dit is nu een van de meest actieve werkgebieden in de PostgreSQL-gemeenschap. PostgreSQL Versie 12 zal worden geleverd met nog meer prestatieverbeteringen in de partitioneringsruimte. Versie 12 wordt naar verwachting in november 2019 uitgebracht.