PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Как да се възползвате от новите функции за разделяне в PostgreSQL 11

Какво е разделяне?

Разделянето разделя големите таблици на по-малки части, което помага за увеличаване на производителността на заявките, улесняване на задачите по поддръжка, подобряване на ефективността на архивирането на данни и по-бързо архивиране на база данни. Можете да прочетете повече за разделянето на PostgreSQL в нашия блог „Ръководство за разделяне на данни в PostgreSQL“.

С неотдавнашното издание на PostgreSQL 11 има много нови невероятни функции за разделяне. Подробностите за тези нови функции за разделяне ще бъдат разгледани в този блог с няколко примера за код.

Актуализиране на ключовете на дяла

Преди PostgreSQL 11 изявлението за актуализиране, което променя стойността на ключа на дяла, беше ограничено и неразрешено. Това вече е възможно в новата версия. Изявлението за актуализиране може да промени стойността на ключа на дяла; всъщност премества редовете към правилната таблица на дялове. Под капака той основно изпълнява DELETE ОТ стар дял и INSERT в нов дял ( DELETE + INSERT).

Добре, нека тестваме това. Създайте таблица и проверете как работи актуализацията на ключа на дяла.

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)

Внимание:АКТУАЛИЗИРАНЕТО ще излезе с грешка, ако няма таблица с дялове по подразбиране и актуализираните стойности не съвпадат с критериите за дялове в нито една дъщерна таблица.

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]

Създаване на дял по подразбиране

Функцията за дялове по подразбиране на PostgreSQL 11 съхранява кортежи, които не се съпоставят с никой друг дял. Преди PostgreSQL 11 тези редове биха излезли от грешка. Ред, който не е съпоставен с никоя таблица на дялове, ще бъде вмъкнат в дяла по подразбиране.

Лабораторен пример:Кодът на държавата „САЩ“ не е дефиниран в таблицата на дяловете по-долу, но въпреки това се вмъква успешно в таблицата по подразбиране.

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

Внимание:Дялът по подразбиране ще предотврати всяко добавяне на нов дял, ако стойността на този дял съществува в таблицата по подразбиране. В този случай „USA“ съществуваше в дяла по подразбиране, така че няма да работи както по-долу.

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 :

Дялът по подразбиране не може да бъде определен за HASH разделена таблица. Не може да има повече от една таблица по подразбиране за таблица на дялове.

Разделяне на хеш

Това е нов механизъм за дялове, ако не можете да решите за дял от диапазон или списък (тъй като не сте сигурни колко голяма би била кофата). Хеш разделянето решава този проблем с разпространението на данни.

Таблицата се разделя чрез посочване на модул и остатък за всеки дял. Всеки дял ще съдържа редовете, за които хеш стойността на ключа на дяла, разделена на посочения модул, ще произведе посочения остатък. Функцията HASH гарантира, че редовете ще бъдат разпределени предимно равномерно в цялата таблица на дяловете.

За начало трябва да решите колко числа от таблицата на дяловете са необходими и съответно модулът и остатъкът могат да бъдат определени; ако модулът е 4, остатъкът може да бъде само от [0-3].

[Модул - Брой таблици | Остатък – коя стойност на остатъка отива в коя кофа ]

Как да настроите хеш дял

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

Вмъкнете 50k записа в родителската таблица:

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

и вижте как разпределя записите равномерно в дъщерната таблица ...

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)

Не можем да променим броя на дяловете, посочени от `Modulus` по-рано, така че трябва да планирате много преди изискванията за броя на таблиците с дялове.

Ще излезе грешка, когато се опитате да добавите нов дял с различен остатък.

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

Хеш разделянето може да работи за всеки тип данни и може да работи и за тип UUID. Винаги се препоръчва броят на таблиците да бъде степен 2 и също така не е задължително да се използва един и същ модул при създаване на таблицата; това ще помогне да създадете таблицата на дяловете по-късно, както е необходимо.

Тази реализация също така би направила вакуумирането по-бързо и може да даде възможност за съединяване на дялове.

Поддръжка за външни ключове

Преди PostgreSQL 11 външният ключ в таблицата на дяловете не се поддържаше. Външните ключове са възможни в таблицата на дяловете сега и по-долу е как...

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

Автоматично създаване на индекс върху дъщерни таблици

В предишни версии на PostgreSQL беше ръчно усилие да се създаде индекс за всяка таблица на дялове. В PostgreSQL версия 11 е доста удобно за потребителите. След като индексът бъде създаден в главната таблица, той автоматично ще създаде индекса със същата конфигурация на всички съществуващи дъщерни дялове и ще се погрижи и за всички бъдещи таблици на дялове.

Индекс, създаден в основната таблица

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

Той автоматично създаде индекса на всички дъщерни таблици, както е показано по-долу. ( Проверете с каталожна таблица)

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)

Индексът може да бъде създаден само на главна таблица, не може да бъде на дъщерна таблица. Автоматично генерираните индекси не могат да бъдат изтривани поотделно.

Автоматично създаване на тригери върху дъщерни таблици

След като тригерът бъде създаден в главната таблица, той автоматично ще създаде тригера за всички дъщерни таблици (това поведение е подобно на това, което се вижда за индекс).

Възможност за създаване на уникален индекс

Във версия 11 уникални индекси могат да бъдат добавени към главната таблица, което ще създаде уникалното ограничение за всички съществуващи дъщерни таблици и бъдещи таблици на дялове.

Нека създадем главна таблица с уникални ограничения.

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

Уникалното ограничение е създадено върху дъщерна таблица автоматично, както е по-долу.

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)

Внимание:Уникалното ограничение за родителската таблица всъщност не гарантира уникалност в цялата йерархия на разделяне. Това не е глобално ограничение, то е само локално.

Изтеглете Бялата книга днес Управление и автоматизация на PostgreSQL с ClusterControl Научете какво трябва да знаете, за да внедрите, наблюдавате, управлявате и мащабирате PostgreSQLD Изтеглете Бялата книга

По-бърза производителност на заявката

Динамично подрязване на дял

В PostgreSQL 11 двоичното търсене позволява по-бързо идентифициране на необходимите дъщерни таблици, независимо дали са разделени на LIST или RANGE. Хеширащата функция намира съвпадащия дял за HASH дял. Всъщност динамично елиминира таблицата(ите) на дяловете, които не се изискват, и повишава производителността на заявката.

Подрязването на динамичния дял може да се контролира от параметъра `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)

Другата страхотна реализация е такава.

Изрязване на дял по време на изпълнение

Във версиите на PostgreSQL преди 11, подрязването на дял може да се случи само по време на план; planner изисква стойност на ключа за дял за да идентифицирате правилния дял. Това поведение е фиксирано в PostgreSQL 11, тъй като плановникът за време на изпълнение ще знае каква стойност се предоставя и въз основа на това, че изборът/елиминирането на дял е възможен и би работил много по-бързо. Случаят на използване може да бъде заявка, която използва параметър (подготвен израз) ИЛИ подзаявка, която предоставя стойността като параметър.

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)

В обяснения на плана по-горе можем да видим, че в момента на изпълнение, плановникът в движение идентифицира правилната таблица на дялове въз основа на стойността на параметъра и работи много по-бързо и не прекарва време в сканиране/зацикляне на друга таблица на дялове (вижте никога изпълнен раздел в плана за обяснение по-горе). Това е много мощно и започна нова ера на подобряване на производителността при разделянето на дялове.

Разделяне Wise Aggregate

Параметър:enable_partitionwise_aggregate

Ако ключът на дяла съвпада с ключа за групиране, всеки дял ще създаде дискретен набор от групи, вместо да сканира целия дял наведнъж. Той ще направи паралелния агрегат за всеки дял и по време на крайния резултат ще конкатенира всички резултати.

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)

Това със сигурност е по-бързо, тъй като включва паралелна обработка на агрегиране и сканиране на дял.

Заявката за каталог може да се използва за познаване на всички родителски таблици на дялове.

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

Кратка матрица на функциите на дяла

Функции за разделяне v11 v10
Дял по подразбиране ДА НЕ
Наследяване на чужда таблица ДА НЕ
Разделяне по хеш ключ ДА НЕ
Поддръжка за PK &FK ДА НЕ
АКТУАЛИЗИРАНЕ на ключ на дял ДА НЕ
Автоматизирани Inexes на CT ДА НЕ
Автоматични задействания на CT ДА НЕ
Отрязване на дяла по време на изпълнение ДА НЕ
Присъединяване с разделяне ДА НЕ
Динамично изрязване на дял ДА НЕ

Какво следва?

Ефективност на разделяне

Това е една от най-активните работни области сега в общността на PostgreSQL. PostgreSQL Версия 12 ще бъде пакетирана с още повече подобрения в производителността в пространството за разделяне. Очаква се версия 12 да излезе през ноември 2019 г.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Spring Data JPA + Hibernate Пропускане на заключени редове (PostgreSQL)

  2. Кой е най-добрият PostgreSQL GUI? Сравнение за 2021 г

  3. PostgreSQL Connection Pooling:Част 3 – Pgpool-II

  4. Как да добавите брой работни дни към дадена дата

  5. Странно съобщение за грешка в SQLAlchemy:TypeError:обектът 'dict' не поддържа индексиране