Was ist Partitionierung?
Durch die Partitionierung werden große Tabellen in kleinere Teile aufgeteilt, wodurch die Abfrageleistung gesteigert, Wartungsaufgaben vereinfacht, die Effizienz der Datenarchivierung verbessert und Datenbanksicherungen beschleunigt werden. Weitere Informationen zur PostgreSQL-Partitionierung finden Sie in unserem Blog „A Guide to Partitioning Data In PostgreSQL“.
Mit der jüngsten Veröffentlichung von PostgreSQL 11 gibt es viele neue erstaunliche Partitionierungsfunktionen. Die Details dieser neuen Partitionierungsfunktionen werden in diesem Blog mit einigen Codebeispielen behandelt.
Aktualisieren der Partitionsschlüssel
Vor PostgreSQL 11 war die Update-Anweisung, die den Wert des Partitionsschlüssels ändert, eingeschränkt und nicht zulässig. Dies ist nun in der neuen Version möglich. Update-Anweisung kann den Wert des Partitionsschlüssels ändern; Es verschiebt die Zeilen tatsächlich in die richtige Partitionstabelle. Unter der Haube führt es im Wesentlichen DELETE FROM old partition und INSERT into new partition aus (DELETE + INSERT).
Okay, testen wir das mal. Erstellen Sie eine Tabelle und überprüfen Sie, wie das Update auf dem Partitionsschlüssel funktioniert.
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)
Achtung:Das UPDATE schlägt fehl, wenn es keine Standard-Partitionstabelle gibt und die aktualisierten Werte nicht mit den Partitionskriterien in einer untergeordneten Tabelle übereinstimmen.
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]
Erstellen einer Standardpartition
Die PostgreSQL 11 DEFAULT-Partitionsfunktion speichert Tupel, die keiner anderen Partition zugeordnet sind. Vor PostgreSQL 11 traten bei diesen Zeilen Fehler auf. Eine Zeile, die keiner Partitionstabelle zugeordnet ist, wird in die Standardpartition eingefügt.
Lab-Beispiel:Der Ländercode „USA“ wurde in der Partitionstabelle unten nicht definiert, wird aber dennoch erfolgreich in die Standardtabelle eingefügt.
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
Achtung:Die Standardpartition verhindert das Hinzufügen neuer Partitionen, wenn dieser Partitionswert in der Standardtabelle vorhanden ist. In diesem Fall existierte „USA“ in der Standardpartition, sodass es nicht wie unten funktioniert.
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-Partition kann nicht für HASH-partitionierte Tabelle angegeben werden. Es kann nicht mehr als eine DEFAULT-Tabelle für die Partitionstabelle geben.
Hash-Partitionierung
Es ist ein neuer Partitionsmechanismus, falls Sie sich nicht für eine Bereichs- oder Listenpartition entscheiden können (weil Sie sich nicht sicher sind, wie groß der Bucket wäre). Hash-Partitionierung löst dieses Datenverteilungsproblem.
Die Tabelle wird partitioniert, indem für jede Partition ein Modulus und ein Rest angegeben werden. Jede Partition enthält die Zeilen, für die der Hash-Wert des Partitionsschlüssels dividiert durch den angegebenen Modulus den angegebenen Rest ergibt. Die HASH-Funktion stellt sicher, dass die Zeilen weitgehend gleichmäßig in der gesamten Partitionstabelle verteilt werden.
Zunächst müssen Sie entscheiden, wie viele Zahlen der Partitionstabelle benötigt werden, und dementsprechend können Modul und Rest definiert werden; wenn der Modulus 4 wäre, kann der Rest nur von [0-3] sein.
[Modul - Anzahl der Tabellen | Rest - Welcher Restwert geht in welchen Bucket ]
So richten Sie eine Hash-Partition ein
-- 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);
Fügen Sie 50.000 Datensätze in die übergeordnete Tabelle ein:
severalnines_v11=# INSERT INTO part_hash_test SELECT generate_series(0,50000);
INSERT 0 50001
und sehen Sie, wie es Datensätze gleichmäßig in der untergeordneten Tabelle verteilt ...
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)
Wir können die von `Modulus` angegebene Anzahl der Partitionen nicht ändern, daher müssen Sie die Anforderungen für die Anzahl der Partitionstabellen vorher gut planen.
Es wird ein Fehler ausgegeben, wenn Sie versuchen, eine neue Partition mit einem anderen Rest hinzuzufügen.
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);
Die Hash-Partitionierung kann für jeden Datentyp und auch für den UUID-Typ funktionieren. Es wird immer empfohlen, dass die Anzahl der Tabellen eine Potenz von 2 sein sollte, und es ist auch nicht zwingend erforderlich, beim Erstellen der Tabelle denselben Modul zu verwenden; dies hilft später bei Bedarf beim Erstellen der Partitionstabelle.
Diese Implementierung würde auch das Vakuum schneller machen und kann partitionsweises Verbinden ermöglichen.
Unterstützung für Fremdschlüssel
Vor PostgreSQL 11 wurde der Fremdschlüssel in der Partitionstabelle nicht unterstützt. Die Fremdschlüssel sind jetzt in der Partitionstabelle möglich und unten ist wie ...
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
Automatische Indexerstellung für untergeordnete Tabellen
In früheren Versionen von PostgreSQL war es ein manueller Aufwand, einen Index für jede Partitionstabelle zu erstellen. In PostgreSQL Version 11 ist es für Benutzer sehr praktisch. Sobald der Index für die Haupttabelle erstellt wurde, erstellt er automatisch den Index mit derselben Konfiguration für alle vorhandenen untergeordneten Partitionen und kümmert sich auch um alle zukünftigen Partitionstabellen.
Index auf Master-Tabelle erstellt
severalnines_v11=# CREATE index idx_name ON customers(cust_name);
CREATE INDEX
Der Index wurde automatisch für alle untergeordneten Tabellen wie unten erstellt. (Mit Katalogtabelle prüfen)
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)
Der Index kann nur für eine Haupttabelle erstellt werden, er kann nicht für eine untergeordnete Tabelle erstellt werden. Automatisch generierte Indizes können nicht einzeln gelöscht werden.
Automatische Trigger-Erstellung auf untergeordneten Tabellen
Sobald der Trigger für die Master-Tabelle erstellt wurde, wird er automatisch für alle untergeordneten Tabellen erstellt (dieses Verhalten ähnelt dem für den Index).
Kann einen eindeutigen Index erstellen
In Version 11 können eindeutige Indizes zur Haupttabelle hinzugefügt werden, wodurch die Eindeutigkeitsbeschränkung für alle vorhandenen untergeordneten Tabellen und zukünftigen Partitionstabellen erstellt wird.
Lassen Sie uns eine Haupttabelle mit eindeutigen Einschränkungen erstellen.
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);
Die Eindeutigkeitsbeschränkung wurde automatisch wie unten für die untergeordnete Tabelle erstellt.
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)
Achtung:Eine Eindeutigkeitsbeschränkung für die übergeordnete Tabelle garantiert nicht wirklich die Eindeutigkeit über die gesamte Partitionierungshierarchie hinweg. Es handelt sich nicht um eine globale Einschränkung, sondern nur um eine lokale.
Laden Sie noch heute das Whitepaper PostgreSQL-Verwaltung und -Automatisierung mit ClusterControl herunterErfahren Sie, was Sie wissen müssen, um PostgreSQL bereitzustellen, zu überwachen, zu verwalten und zu skalierenLaden Sie das Whitepaper herunterSchnellere Abfrageleistung
Dynamisches Partitions-Pruning
In PostgreSQL 11 ermöglicht die binäre Suche eine schnellere Identifizierung erforderlicher untergeordneter Tabellen, unabhängig davon, ob es sich um LIST- oder RANGE-Partitionen handelt. Die Hash-Funktion findet die passende Partition für die HASH-Partition. Es eliminiert tatsächlich dynamisch die Partitionstabelle(n), die nicht benötigt werden, und steigert die Abfrageleistung.
Das dynamische Löschen von Partitionen kann durch den Parameter `enable_partition_pruning` gesteuert werden.
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)
Die andere großartige Implementierung sieht so aus.
Partitionsbereinigung zur Ausführungszeit
In PostgreSQL-Versionen vor 11 kann die Partitionsbereinigung nur zur Planzeit erfolgen; planner erfordert einen Wert des Partitionsschlüssels um die richtige Partition zu identifizieren. Dieses Verhalten wurde in PostgreSQL 11 behoben, da der Ausführungszeitplaner wissen würde, welcher Wert geliefert wird, und basierend auf dieser Partitionsauswahl / -eliminierung möglich ist und viel schneller laufen würde. Der Anwendungsfall kann eine Abfrage sein, die einen Parameter (vorbereitete Anweisung) ODER eine Unterabfrage verwendet, die den Wert als Parameter bereitstellt.
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 der Erklärung des obigen Plans können wir sehen, dass der Planer zum Zeitpunkt der Ausführung die richtige Partitionstabelle anhand des Parameterwerts identifizierte und viel schneller lief und keine Zeit für das Scannen/Schleifen anderer Partitionstabellen aufwendete (siehe never ausgeführter Abschnitt im Erklärungsplan oben). Dies ist sehr leistungsfähig und hat eine neue Ära der Leistungssteigerung bei der Partitionierung eingeleitet.
Partitionsweises Aggregat
Parameter:enable_partitionwise_aggregate
Wenn der Partitionsschlüssel mit dem Gruppierungsschlüssel übereinstimmt, erstellt jede Partition einen separaten Satz von Gruppen, anstatt die gesamte Partition auf einmal zu scannen. Es führt die parallele Aggregation für jede Partition durch und verkettet während des Endergebnisses alle Ergebnisse.
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)
Dies ist sicherlich schneller, da es die parallele Aggregationsverarbeitung und das Scannen pro Partition umfasst.
Die Katalogabfrage kann verwendet werden, um alle übergeordneten Partitionstabellen zu kennen.
SELECT relname FROM pg_class WHERE oid in (select partrelid FROM pg_partitioned_table);
Kurze Partitions-Feature-Matrix
Partitionierungsfunktionen | v11 | v10 |
---|---|---|
Standardpartition | JA | NEIN |
Fremdtabellenvererbung | JA | NEIN |
Partitionierung nach Hash-Schlüssel | JA | NEIN |
Unterstützung für PK &FK | JA | NEIN |
UPDATE auf einem Partitionsschlüssel | JA | NEIN |
Automatisierte Indizes auf CT | JA | NEIN |
Automatisierte Trigger auf CT | JA | NEIN |
Bereinigung der Ausführungszeitpartition | JA | NEIN |
Partitionsweiser Join | JA | NEIN |
Dynamische Partitionsbereinigung | JA | NEIN |
Wie geht es weiter?
Partitionierungsleistung
Dies ist derzeit einer der aktivsten Arbeitsbereiche in der PostgreSQL-Community. PostgreSQL Version 12 wird mit noch mehr Leistungsverbesserungen im Bereich der Partitionierung ausgeliefert. Version 12 wird voraussichtlich im November 2019 veröffentlicht.