sql >> Databáze >  >> RDS >> PostgreSQL

Jak využít nové funkce dělení v PostgreSQL 11

Co je to rozdělení?

Rozdělení velkých tabulek rozděluje velké tabulky na menší části, což pomáhá zvýšit výkon dotazů, usnadňuje údržbu, zlepšuje efektivitu archivace dat a urychluje zálohování databáze. Více o rozdělování PostgreSQL si můžete přečíst v našem blogu “Průvodce rozdělováním dat v PostgreSQL”.

S nedávným vydáním PostgreSQL 11 existuje mnoho nových úžasných funkcí dělení. Podrobnosti o těchto nových funkcích rozdělení budou popsány v tomto blogu s několika příklady kódu.

Aktualizace klíčů oddílu

Před PostgreSQL 11 byl příkaz Update, který mění hodnotu klíče oddílu, omezen a nebyl povolen. To je nyní možné v nové verzi. Příkaz Update může změnit hodnotu klíče oddílu; ve skutečnosti přesune řádky do správné tabulky oddílů. Pod kapotou v podstatě provede DELETE FROM starého oddílu a INSERT do nového oddílu ( DELETE + INSERT).

Dobře, pojďme to otestovat. Vytvořte tabulku a ověřte, jak aktualizace funguje na klíči oddílu.

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)

Upozornění:Pokud neexistuje žádná výchozí tabulka oddílů a aktualizované hodnoty neodpovídají kritériím oddílu v žádné podřízené tabulce, dojde k chybě UPDATE.

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]

Vytvoření výchozího oddílu

Funkce oddílů PostgreSQL 11 DEFAULT ukládá n-tice, které se nemapují na žádný jiný oddíl. Před PostgreSQL 11 se tyto řádky vyskytly chybně. Řádek, který není namapován na žádnou tabulku oddílů, bude vložen do výchozího oddílu.

Příklad laboratoře:Kód země `USA` nebyl definován v tabulce oddílů níže, ale přesto se úspěšně vložil do výchozí tabulky.

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

Upozornění:Výchozí oddíl zabrání přidání nového oddílu, pokud tato hodnota oddílu existuje ve výchozí tabulce. V tomto případě ve výchozím oddílu existovalo „USA“, takže nebude fungovat jako níže.

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 oddíl nelze zadat pro HASH dělenou tabulku. Pro tabulku oddílů nemůže být více než jedna DEFAULT tabulka.

Rozdělení hashů

Je to nový mechanismus rozdělení, pokud se nemůžete rozhodnout pro rozsah nebo seznam oddílů (protože si nejste jisti, jak velký by byl kbelík). Tento problém s distribucí dat řeší rozdělení hashů.

Tabulka je rozdělena určením modulu a zbytku pro každý oddíl. Každý oddíl bude obsahovat řádky, pro které hodnota hash klíče oddílu dělená zadaným modulem vytvoří zadaný zbytek. Funkce HASH zajišťuje, že řádky budou rozmístěny většinou rovnoměrně v celé tabulce oddílů.

Nejprve se musíte rozhodnout, kolik čísel z tabulky oddílů je požadováno, a podle toho lze definovat modul a zbytek; pokud by byl modul 4, zbytek může být pouze od [0-3].

[Modul - Počet stolů | Remainder – Která hodnota zbytku jde do kterého segmentu ]

Jak nastavit hashovací oddíl

-- 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);

Vložit 50 000 záznamů do nadřazené tabulky:

severalnines_v11=# INSERT INTO part_hash_test SELECT generate_series(0,50000);
INSERT 0 50001

a podívejte se, jak rovnoměrně rozdělil záznamy v podřízené tabulce ...

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)

Nemůžeme změnit počet oddílů zadaný `Modulem` dříve, takže musíte plánovat s dostatečným předstihem před požadavky na počet tabulek oddílů.

Při pokusu o přidání nového oddílu s jiným zbytkem dojde k chybě.

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);

Rozdělení hash může fungovat na jakémkoli datovém typu a může fungovat i pro typ UUID. Vždy se doporučuje, aby počet tabulek byl umocněn 2, a také není povinné používat stejný modul při vytváření tabulky; to pomůže vytvořit tabulku oddílů později podle potřeby.

Tato implementace by také zrychlila vakuum a mohla by umožnit připojení podle oddílu.

Podpora cizích klíčů

Před PostgreSQL 11 nebyl cizí klíč v tabulce oddílů podporován. Cizí klíče jsou nyní možné v tabulce oddílů a níže je postup...

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

Automatické vytváření indexů v podřízených tabulkách

V předchozích verzích PostgreSQL to bylo ruční úsilí vytvořit index na každé tabulce oddílů. Ve verzi PostgreSQL 11 je to pro uživatele docela pohodlné. Jakmile je index vytvořen na hlavní tabulce, automaticky vytvoří index se stejnou konfigurací na všech existujících podřízených oddílech a postará se také o všechny budoucí tabulky oddílů.

Index vytvořen na hlavní tabulce

severalnines_v11=# CREATE index idx_name ON customers(cust_name);
CREATE INDEX

Automaticky vytvořil index na všech podřízených tabulkách, jak je uvedeno níže. (Ověřte pomocí katalogové tabulky)

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 lze vytvořit pouze na hlavní tabulce, nemůže být na podřízené tabulce. Automaticky generované indexy nelze jednotlivě smazat.

Vytváření automatického spouštění v podřízených tabulkách

Jakmile je spouštěč vytvořen na hlavní tabulce, automaticky vytvoří spouštěč na všech podřízených tabulkách (toto chování je podobné chování zaznamenanému u indexu).

Schopnost vytvořit jedinečný index

Ve verzi 11 lze do hlavní tabulky přidat jedinečné indexy, které vytvoří jedinečné omezení pro všechny existující podřízené tabulky a budoucí tabulky oddílů.

Pojďme vytvořit hlavní tabulku s jedinečnými omezeními.

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);

Jedinečné omezení bylo v podřízené tabulce vytvořeno automaticky, jak je uvedeno níže.

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)

Upozornění:Jedinečné omezení nadřazené tabulky ve skutečnosti nezaručuje jedinečnost v celé hierarchii rozdělení. Není to globální omezení, je to pouze lokální.

Stáhněte si Whitepaper Today Správa a automatizace PostgreSQL s ClusterControlZjistěte, co potřebujete vědět k nasazení, monitorování, správě a škálování PostgreSQLStáhněte si Whitepaper

Rychlejší výkon dotazů

Dynamické ořezávání oddílu

V PostgreSQL 11 umožňuje binární vyhledávání rychlejší identifikaci požadovaných podřízených tabulek, ať už jsou rozděleny na LIST nebo RANGE. Hašovací funkce najde odpovídající oddíl pro oddíl HASH. Ve skutečnosti dynamicky odstraňuje tabulky oddílů, které nejsou vyžadovány, a zvyšuje výkon Query.

Prořezávání dynamického oddílu lze ovládat parametrem `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)

Další úžasná implementace je taková.

Prořezávání oddílu v době provádění

Ve verzích PostgreSQL starších než 11 může prořezávání oddílů probíhat pouze v době plánu; plánovač vyžaduje hodnotu klíče oddílu k identifikaci správného oddílu. Toto chování je opraveno v PostgreSQL 11, protože plánovač doby provádění by věděl, jaká hodnota se dodává, a na základě toho je výběr / eliminace oddílu možný a běžel by mnohem rychleji. Případem užití může být dotaz, který používá parametr (připravený příkaz) NEBO poddotaz, který poskytuje hodnotu 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)

Ve výše uvedeném plánu vysvětlení můžeme vidět, že v době provádění plánovač za běhu identifikoval správnou tabulku oddílů na základě hodnoty parametru a běžel mnohem rychleji a neztrácel čas skenováním/smyčkou na jiné tabulce oddílů (viz nikdy provedená část v plánu vysvětlení výše). To je velmi výkonné a odstartovalo novou éru vylepšování výkonu v oblasti dělení.

Rozdělení Wise Aggregate

Parametr:enable_partitionwise_aggregate

Pokud se klíč oddílu shoduje s klíčem seskupení, každý oddíl vytvoří samostatnou sadu skupin namísto skenování celého oddílu najednou. Provede paralelní agregaci pro každý oddíl a během konečného výsledku zřetězí všechny výsledky.

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)

To je jistě rychlejší, protože zahrnuje paralelní zpracování agregace a skenování podle oddílu.

Katalogový dotaz lze použít ke zjištění všech nadřazených tabulek oddílů.

SELECT relname FROM pg_class WHERE oid in (select partrelid FROM  pg_partitioned_table);

Stručná matice funkcí oddílu

Funkce rozdělení v11 v10
Výchozí oddíl ANO NE
Dědičnost cizí tabulky ANO NE
Rozdělení podle hash klíče ANO NE
Podpora pro PK a FK ANO NE
AKTUALIZACE klíče oddílu ANO NE
Automatické Inexes na CT ANO NE
Automatické spouštění na CT ANO NE
Prořezávání oddílu v době provádění ANO NE
Připoj se podle oddílu ANO NE
Prořezání dynamického oddílu ANO NE

Co dál?

Výkon rozdělování

Toto je nyní jedna z nejaktivnějších pracovních oblastí v komunitě PostgreSQL. PostgreSQL verze 12 bude obsahovat ještě další vylepšení výkonu v oblasti dělení. Verze 12 by měla být vydána v listopadu 2019.


  1. Oprava „Nelze nainstalovat v Homebrew na procesor ARM ve výchozí předponě Intel (/usr/local)!“

  2. SQL vybrat vše, pokud je parametr null, jinak vrátit konkrétní položku

  3. Jak převést datum a čas na hodnotu unixové epochy v Postgresu?

  4. Funkce NLS_INITCAP() v Oracle