Система за разделяне в PostgreSQL беше добавена за първи път в PostgreSQL 8.1 от основателя на 2ndQuadrant Саймън Ригс . Тя се основава на наследяване на релации и използва нова техника за изключване на таблици от сканиране чрез заявка, наречена „изключване на ограничения“. Въпреки че това беше огромна крачка напред по онова време, днес се смята, че е тромав за използване, както и бавен и поради това се нуждае от подмяна.
Във версия 10 той беше заменен благодарение на героични усилия отАмит Ланготе с "декларативно разделяне" в модерен стил. Тази нова технология означаваше, че вече не е необходимо да пишете код ръчно за насочване на кортежи към правилните им дялове и вече не е необходимо ръчно да декларирате правилни ограничения за всеки дял:системата направи тези неща автоматично вместо вас.
За съжаление, в PostgreSQL 10 това е почти всичко, което направи. Поради голямата сложност и ограниченията във времето, липсваха много неща в реализацията на PostgreSQL 10. Робърт Хаас говори за това на PGConf.EU във Варшава.
Много хора работиха за подобряване на ситуацията за PostgreSQL 11; ето моя опит за преброяване. Разделям ги на три области:
- Нови функции за разделяне
- По-добра поддръжка на DDL за разделени таблици
- Оптимизации на производителността.
Нови функции за разделяне
В PostgreSQL 10 вашите разделени таблици могат да бъдат в RANGE и СПИСЪК режими. Това са мощни инструменти за базиране на много бази данни в реалния свят, но за много други дизайни се нуждаете от новия режим, добавен в PostgreSQL 11:HASH преграждането . Много клиенти се нуждаят от това иАмул Сул работи усилено, за да стане възможно. Ето един прост пример:
CREATE TABLE clients ( client_id INTEGER, name TEXT ) PARTITION BY HASH (client_id); CREATE TABLE clients_0 PARTITION OF clients FOR VALUES WITH (MODULUS 3, REMAINDER 0); CREATE TABLE clients_1 PARTITION OF clients FOR VALUES WITH (MODULUS 3, REMAINDER 1); CREATE TABLE clients_2 PARTITION OF clients FOR VALUES WITH (MODULUS 3, REMAINDER 2);
Не е задължително да се използва една и съща стойност на модула за всички дялове; това ви позволява да създавате повече дялове по-късно и да преразпределяте редовете един по един дял, ако е необходимо.
Друга много полезна функция, написана от Амит Хандекар е възможността за разрешаване на АКТУАЛИЗИРАНЕ за преместване на редове от един дял в друг — тоест, ако има промяна в стойностите на колоната за разделяне, редът автоматично се премества в правилния дял. По-рано тази операция щеше да доведе до грешка.
Друга нова функция, написана от Amit Langote и тас искрено , това е ВМЪКВАНЕ ПРИ АКТУАЛИЗИРАНЕ НА КОНФЛИКТ може да се прилага към разделени таблици . Преди това тази команда би се провалила, ако беше насочена към разделена таблица. Можете да го накарате да работи, като знаете точно в кой дял ще се окаже редът, но това не е много удобно. Няма да разглеждам подробностите за тази команда, но ако някога сте искали да имате UPSERT в Postgres, това е всичко. Едно предупреждение е, че UPDATE действието може да не премести реда в друг дял.
И накрая, още една сладка нова функция в PostgreSQL 11, този път от Jeevan Ladhe, Beena Emerson, Ashutosh Bapat, Rahila Syed, иРобърт Хаас е поддръжка за дял по подразбиране в разделена таблица , тоест дял, който получава всички редове, които не се вписват в нито един от редовните дялове. Въпреки това, макар и хубава на хартия, тази функция не е много удобна за производствените настройки, тъй като някои операции изискват по-тежко заключване с дялове по подразбиране, отколкото без. Пример:създаването на нов дял изисква сканиране на дяла по подразбиране, за да се определи, че няма съществуващи редове да съответстват на границите на новия дял. Може би в бъдеще тези изисквания за заключване ще бъдат намалени, но междувременно моето предложение е да не го използвате.
По-добра поддръжка на DDL
В PostgreSQL 10 някои DDL ще откажат да работят, когато се прилагат към разделена таблица и изискват от вас да обработвате всеки дял поотделно. В PostgreSQL 11 поправихме някои от тези ограничения, както беше обявено по-рано от Саймън Ригс. Първо, сега можете да използвате CREATE INDEX на разделена маса , функция, написана от вас наистина. Това може да се разглежда просто като въпрос на намаляване на досадата:вместо да повтаряте командата за всеки дял (и да се уверите, че никога не забравяте за всеки нов дял), можете да го направите само веднъж за родителската разделена таблица и тя се прилага автоматично към всички дялове, съществуващи и бъдещи.
Едно страхотно нещо, което трябва да имате предвид, е съпоставянето на съществуващите индекси в дяловете. Както знаете, създаването на индекс е блокиращо предложение, така че колкото по-малко време отнема, толкова по-добре. Написах тази функция, така че съществуващите индекси в дяла да се сравняват с индексите, които се създават, и ако има съвпадения, не е необходимо да сканирате дяла, за да създадете нови индекси:ще се използват съществуващите индекси.
Заедно с това, също от ваша страна, можете също така да създадете УНИКАЛНО ограничения, както и PRIMARY KEY ограничения . Две предупреждения:първо, ключът на дяла трябва да бъде част от първичния ключ. Това позволява уникалните проверки да се извършват локално на дял, като се избягват глобалните индекси. Второ, все още не е възможно да имате външни ключове, които препращат към тези първични ключове. Работя върху това за PostgreSQL 12.
Друго нещо, което можете да направите (благодарение на същия човек) е да създадете ЗА ВСЕКИ РЕД тригери на разделена таблица , и да се прилага към всички дялове (съществуващи и бъдещи). Като страничен ефект можете да имате отложен уникален ограничения върху разделени таблици. Едно предупреждение:само СЛЕД тригерите са разрешени, докато не разберем как да се справим с ПРЕДИ тригери, които преместват редове към различен дял.
И накрая, разделената таблица може да има ВЪНШЕН КЛЮЧ ограничения . Това е много удобно за разделяне на големи таблици с факти, като същевременно избягвате висящи препратки, които всички мразят. Колегата ми Габриеле Бартолини ме сграбчи в скута ми, когато разбра, че съм написал и извършил това, крещейки, че това е променило в играта и как можех да бъда толкова безчувствен, че да не го информирам за това. Аз просто продължавам да хаквам кода за забавление.
Работа с производителност
Преди това предварителната обработка на заявките, за да разберете кои дялове да не се сканират (изключване на ограничения), беше доста опростена и бавна. Това беше подобрено от възхитителната екипна работа, осъществена от Амит Ланготе, Дейвид Роули, Бийна Емерсън, Дилип Кумар, за да се въведе първо „по-бързо подрязване“ и „подрязване по време на изпълнение“ въз основа на това след това. Резултатът е много по-мощен, както и по-бърз (Дейвид Роули вече беше описано това в предишна статия.) След всички тези усилия, подрязването на дял се прилага в три точки от живота на заявката:
- По време на плана на заявката,
- Когато се получат параметрите на заявката,
- Във всяка точка, където един възел на заявка предава стойности като параметри на друг възел.
Това е забележително подобрение от оригиналната система, което може да се приложи само по време на план за заявка и вярвам, че ще се хареса на мнозина.
Можете да видите тази функция в действие, като сравните изхода EXPLAIN за заявка преди и след изключване на enable_partition_pruning опция. Като много опростен пример, сравнете този план без подрязване:
SET enable_partition_pruning TO off; EXPLAIN (ANALYZE, COSTS off) SELECT * FROM clientes WHERE cliente_id = 1234;
QUERY PLAN ------------------------------------------------------------------------- Append (actual time=6.658..10.549 rows=1 loops=1) -> Seq Scan on clientes_1 (actual time=4.724..4.724 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 24978 -> Seq Scan on clientes_00 (actual time=1.914..1.914 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12644 -> Seq Scan on clientes_2 (actual time=0.017..1.021 rows=1 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12570 -> Seq Scan on clientes_3 (actual time=0.746..0.746 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12448 -> Seq Scan on clientes_01 (actual time=0.648..0.648 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12482 -> Seq Scan on clientes_4 (actual time=0.774..0.774 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12400 -> Seq Scan on clientes_5 (actual time=0.717..0.717 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12477 Planning Time: 0.375 ms Execution Time: 10.603 ms
с този, произведен с резитба:
EXPLAIN (ANALYZE, COSTS off) SELECT * FROM clientes WHERE cliente_id = 1234;
QUERY PLAN ---------------------------------------------------------------------- Append (actual time=0.054..2.787 rows=1 loops=1) -> Seq Scan on clientes_2 (actual time=0.052..2.785 rows=1 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12570 Planning Time: 0.292 ms Execution Time: 2.822 ms
Сигурен съм, че ще намерите това за убедително. Можете да видите много по-сложни примери, като прегледате очаквания файл с регресионни тестове.
Друг елемент беше въвеждането на свързване на дялове от Ашутош Бапат . Идеята тук е, че ако имате две разделени таблици и те са разделени по идентични начини, тогава когато се присъединят, можете да присъедините всеки дял от едната страна към съответния дял от другата страна; това е много по-добре от присъединяването на всеки дял отстрани към всеки дял от другата страна. Фактът, че схемите на дялове трябва да съвпадат точно може да изглежда малко вероятно това да има голяма употреба в реалния свят, но в действителност има много ситуации, в които това е приложимо. Пример:таблица с поръчки и съответстващата й таблица orders_items. За щастие вече има много работа по облекчаването на това ограничение.
Последният елемент, който искам да спомена, е агрегатите на дялове, от Jeevan Chalke, Ashutosh Bapat, иРобърт Хаас . Тази оптимизация означава, че агрегиране, което включва ключовете на дяла в GROUP BY Клаузата може да се изпълни чрез агрегиране на редовете на всеки дял поотделно, което е много по-бързо.
Заключителни мисли
След значителните разработки в този цикъл, PostgreSQL има много по-завладяваща история за разделяне. Въпреки че все още има много подобрения, които трябва да бъдат направени, особено за подобряване на производителността и едновременността на различни операции, включващи разделени таблици, сега сме в момент, в който декларативното разделяне се превърна в много ценен инструмент за обслужване на много случаи на употреба. Във 2ndQuadrant ще продължим да допринасяме с код за подобряване на PostgreSQL в тази и други области, както сме правили за всяка отделна версия след 8.0.