Co to jest partycjonowanie?
Partycjonowanie dzieli duże tabele na mniejsze części, co pomaga w zwiększeniu wydajności zapytań, ułatwia wykonywanie zadań konserwacyjnych, poprawia wydajność archiwizacji danych i przyspiesza tworzenie kopii zapasowych baz danych. Możesz przeczytać więcej o partycjonowaniu PostgreSQL w naszym blogu „Przewodnik po partycjonowaniu danych w PostgreSQL”.
W ostatnim wydaniu PostgreSQL 11 pojawiło się wiele nowych, niesamowitych funkcji partycjonowania. Szczegóły tych nowych funkcji partycjonowania zostaną omówione w tym blogu z kilkoma przykładami kodu.
Aktualizacja kluczy partycji
Przed wersją PostgreSQL 11 instrukcja Update zmieniająca wartość klucza partycji była ograniczona i niedozwolona. Jest to teraz możliwe w nowej wersji. Instrukcja aktualizacji może zmienić wartość klucza partycji; faktycznie przenosi wiersze do właściwej tablicy partycji. Pod maską zasadniczo wykonuje DELETE FROM starej partycji i INSERT do nowej partycji (DELETE + INSERT).
W porządku, przetestujmy to. Utwórz tabelę i sprawdź, jak działa aktualizacja na kluczu partycji.
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)
Uwaga:UPDATE spowoduje błąd, jeśli nie ma domyślnej tabeli partycji, a zaktualizowane wartości nie będą zgodne z kryteriami partycji w żadnej tabeli podrzędnej.
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]
Tworzenie partycji domyślnej
Funkcja partycji PostgreSQL 11 DEFAULT przechowuje krotki, które nie są mapowane na żadną inną partycję. Przed PostgreSQL 11 te wiersze zawierały błędy. Wiersz, który nie jest odwzorowany na żadną tabelę partycji, zostanie wstawiony do partycji domyślnej.
Przykład laboratoryjny:kod kraju `USA` nie został zdefiniowany w poniższej tabeli partycji, ale nadal jest pomyślnie wstawiany do tabeli domyślnej.
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
Ostrzeżenie:Partycja domyślna uniemożliwi dodawanie nowych partycji, jeśli ta wartość partycji istnieje w tabeli domyślnej. W tym przypadku `USA` istniało na partycji domyślnej, więc nie będzie działać jak poniżej.
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 :
Nie można określić partycji DEFAULT dla tabeli partycjonowanej HASH. Nie może istnieć więcej niż jedna tabela DOMYŚLNA dla tabeli partycji.
Partycjonowanie haszujące
Jest to nowy mechanizm partycjonowania, jeśli nie możesz zdecydować się na partycję zakresu lub listy (ponieważ nie jesteś pewien, jak duży byłby kubełek). Partycjonowanie haszujące rozwiązuje ten problem z dystrybucją danych.
Tabela jest podzielona na partycje przez określenie modułu i reszty dla każdej partycji. Każda partycja będzie przechowywać wiersze, dla których wartość skrótu klucza partycji podzielona przez określony moduł da określoną resztę. Funkcja HASH zapewnia, że wiersze będą rozmieszczone w większości równomiernie w całej tablicy partycji.
Na początek musisz zdecydować, ile numerów tablicy partycji jest wymaganych i odpowiednio można zdefiniować moduł i resztę; jeśli moduł byłby równy 4, reszta może wynosić tylko od [0-3].
[Moduł - Liczba tabel | Reszta — jaka wartość reszty trafia do którego zasobnika]
Jak skonfigurować partycję mieszającą
-- 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);
Wstaw 50k rekordów w tabeli nadrzędnej:
severalnines_v11=# INSERT INTO part_hash_test SELECT generate_series(0,50000);
INSERT 0 50001
i zobacz, jak równomiernie rozłożył rekordy w tabeli podrzędnej ...
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)
Nie możemy zmienić liczby partycji określonej wcześniej przez `Modulus`, więc musisz zaplanować na długo przed wymaganiami dotyczącymi liczby tabel partycji.
Wystąpi błąd, gdy spróbujesz dodać nową partycję z inną resztą.
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);
Partycjonowanie haszujące może działać na dowolnym typie danych i może działać również na typie UUID. Zawsze zaleca się, aby liczba tabel była potęgą 2 i nie jest również obowiązkowe używanie tego samego modułu podczas tworzenia tabeli; pomoże to później utworzyć tablicę partycji zgodnie z wymaganiami.
Ta implementacja przyspieszy również próżnię i może umożliwić łączenie partycji.
Obsługa kluczy obcych
Przed PostgreSQL 11 klucz obcy w tabeli partycji nie był obsługiwany. Klucze obce są teraz możliwe w tabeli partycji, a poniżej jest jak...
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
Automatyczne tworzenie indeksu w tabelach podrzędnych
W poprzednich wersjach PostgreSQL tworzenie indeksu na każdej tablicy partycji wymagało wysiłku ręcznego. W PostgreSQL w wersji 11 jest to całkiem wygodne dla użytkowników. Po utworzeniu indeksu w tabeli głównej automatycznie utworzy indeks z tą samą konfiguracją na wszystkich istniejących partycjach podrzędnych i zajmie się również wszystkimi przyszłymi tabelami partycji.
Indeks utworzony w tabeli głównej
severalnines_v11=# CREATE index idx_name ON customers(cust_name);
CREATE INDEX
Automatycznie utworzył indeks we wszystkich tabelach podrzędnych, jak poniżej. ( Zweryfikuj za pomocą tabeli katalogu)
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)
Indeks można utworzyć tylko w tabeli głównej, nie może znajdować się w tabeli podrzędnej. Automatycznie wygenerowane indeksy nie mogą być usuwane pojedynczo.
Automatyczne tworzenie wyzwalaczy w tabelach podrzędnych
Po utworzeniu wyzwalacza w tabeli głównej, automatycznie utworzy wyzwalacz we wszystkich tabelach podrzędnych (zachowanie to jest podobne do tego, które zaobserwowano w przypadku indeksu).
Potrafi stworzyć unikalny indeks
W wersji 11 do tabeli głównej można dodać unikalne indeksy, które stworzą unikalne ograniczenie dla wszystkich istniejących tabel podrzędnych i przyszłych tabel partycji.
Stwórzmy tabelę główną z unikalnymi ograniczeniami.
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);
Unikalne ograniczenie zostało automatycznie utworzone w tabeli podrzędnej, jak poniżej.
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)
Przestroga:Ograniczenie unikatowe w tabeli nadrzędnej nie gwarantuje w rzeczywistości unikalności w całej hierarchii partycjonowania. Nie jest to ograniczenie globalne, tylko lokalne.
Pobierz oficjalny dokument już dziś Zarządzanie i automatyzacja PostgreSQL za pomocą ClusterControlDowiedz się, co musisz wiedzieć, aby wdrażać, monitorować, zarządzać i skalować PostgreSQLPobierz oficjalny dokumentSzybsza wydajność zapytań
Dynamiczne przycinanie partycji
W PostgreSQL 11 wyszukiwanie binarne umożliwia szybszą identyfikację wymaganych tabel podrzędnych, niezależnie od tego, czy są one podzielone na partycje LIST lub RANGE. Funkcja mieszająca znajduje partycję pasującą do partycji HASH. W rzeczywistości dynamicznie eliminuje tabele partycji, które nie są wymagane i zwiększa wydajność zapytań.
Oczyszczanie partycji dynamicznej może być kontrolowane przez parametr `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)
Inna niesamowita implementacja jest taka.
Przycinanie partycji w czasie wykonywania
W wersjach PostgreSQL wcześniejszych niż 11, czyszczenie partycji może nastąpić tylko w czasie planowania; planer wymaga wartości klucza partycji aby zidentyfikować właściwą partycję. To zachowanie zostało naprawione w PostgreSQL 11, ponieważ planista czasu wykonania wiedziałby, jaka wartość jest dostarczana, i na tej podstawie wybór / eliminacja partycji jest możliwy i działałby znacznie szybciej. Przypadkiem użycia może być zapytanie, które używa parametru (przygotowanej instrukcji) LUB podzapytanie, które dostarcza wartość jako parametr.
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)
W powyższym planie wyjaśnień widzimy, że w czasie wykonywania planista w locie identyfikował prawidłową tablicę partycji na podstawie wartości parametru, działał znacznie szybciej i nie spędzał czasu na skanowaniu/pętli na innej tablicy partycji (patrz nigdy wykonanej sekcji w wyjaśnieniu planu powyżej). Jest to bardzo potężne i zapoczątkowało nową erę poprawy wydajności partycjonowania.
Agregacja partycjonowania
Parametr:enable_partitionwise_aggregate
Jeśli klucz partycji pasuje do klucza grupującego, każda partycja utworzy dyskretny zestaw grup zamiast skanowania całej partycji naraz. Wykona agregację równoległą dla każdej partycji, a podczas końcowego wyniku połączy wszystkie wyniki.
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)
Jest to z pewnością szybsze, ponieważ obejmuje równoległe przetwarzanie agregacji i skanowanie partycji.
Zapytanie katalogowe może być użyte do poznania wszystkich nadrzędnych tabel partycji.
SELECT relname FROM pg_class WHERE oid in (select partrelid FROM pg_partitioned_table);
Krótka macierz funkcji partycji
Funkcje partycjonowania | v11 | v10 |
---|---|---|
Partycja domyślna | TAK | NIE |
Obce dziedziczenie tabeli | TAK | NIE |
Partycjonowanie według klucza skrótu | TAK | NIE |
Wsparcie dla PK i FK | TAK | NIE |
AKTUALIZACJA na kluczu partycji | TAK | NIE |
Automatyczne indeksy Inexe na CT | TAK | NIE |
Automatyczne wyzwalacze na CT | TAK | NIE |
Przycinanie partycji czasu wykonania | TAK | NIE |
Dołącz do partycji | TAK | NIE |
Dynamiczne przycinanie partycji | TAK | NIE |
Co dalej?
Wydajność partycjonowania
Jest to obecnie jeden z najbardziej aktywnych obszarów pracy w społeczności PostgreSQL. PostgreSQL w wersji 12 zostanie spakowany z jeszcze większą wydajnością w obszarze partycjonowania. Wersja 12 ma zostać wydana w listopadzie 2019 r.