sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur man drar fördel av de nya partitioneringsfunktionerna i PostgreSQL 11

Vad är partitionering?

Partitionering delar upp stora tabeller i mindre bitar, vilket hjälper till att öka frågeprestanda, göra underhållsuppgifter enklare, förbättra effektiviteten i dataarkivering och snabbare säkerhetskopiering av databas. Du kan läsa mer om PostgreSQL-partitionering i vår blogg "A Guide to Partitioning Data In PostgreSQL".

Med den senaste utgåvan av PostgreSQL 11 finns det många nya fantastiska partitioneringsfunktioner. Detaljerna för dessa nya partitioneringsfunktioner kommer att behandlas i den här bloggen med några kodexempel.

Uppdatera partitionsnycklarna

Före PostgreSQL 11 var uppdateringssatsen som ändrar värdet på partitionsnyckeln begränsad och tillåts inte. Detta är nu möjligt i den nya versionen. Uppdateringssats kan ändra värdet på partitionsnyckeln; den flyttar faktiskt raderna till rätt partitionstabell. Under huven körs i princip DELETE FROM gammal partition och INSERT till ny partition (DELETE + INSERT).

Okej, låt oss testa detta. Skapa en tabell och verifiera hur uppdateringen fungerar på partitionsnyckeln.

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)

Varning:UPPDATERING kommer att felas om det inte finns någon standardpartitionstabell och uppdaterade värden inte matchar partitionskriterierna i någon underordnad tabell.

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]

Skapa en standardpartition

PostgreSQL 11 DEFAULT-partitionsfunktionen lagrar tupler som inte mappas till någon annan partition. Före PostgreSQL 11 skulle dessa rader felas. En rad som inte är mappad till någon partitionstabell skulle infogas i standardpartitionen.

Labbexempel:'USA' landskod definierades inte i partitionstabellen nedan, men den infogas ändå framgångsrikt i standardtabellen.

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

Varning:Standardpartitionen förhindrar varje ny partitionstillägg om det partitionsvärdet finns i standardtabellen. I det här fallet fanns 'USA' i standardpartitionen så det kommer inte att fungera som nedan.

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 kan inte anges för HASH-partitionerad tabell. Det kan inte finnas mer än en DEFAULT-tabell för partitionstabellen.

Hash-partitionering

Det är en ny partitionsmekanism, om du inte kan bestämma dig för en intervall- eller listpartition (eftersom du inte är säker på hur stor hinken skulle vara). Hash-partitionering löser detta datadistributionsproblem.

Tabellen partitioneras genom att ange en modul och en rest för varje partition. Varje partition kommer att innehålla raderna för vilka hashvärdet för partitionsnyckeln dividerat med den specificerade modulen kommer att producera den specificerade resten. HASH-funktionen säkerställer att rader fördelas mestadels jämnt i alla partitionstabeller.

Till att börja med måste du bestämma hur många nummer av partitionstabellen som krävs och följaktligen kan modul och återstod definieras; om modulen skulle vara 4, kan resten bara vara från [0-3].

[Modul - Antal tabeller | Remainder - Vilket värde av resterande som går till vilken hink ]

Hur man ställer in en Hash-partition

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

Infoga 50 000 poster i den överordnade tabellen:

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

och se hur den fördelade poster jämnt i den underordnade tabellen ...

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)

Vi kan inte ändra antalet partitioner som specificerats av 'Modulus' tidigare, så du måste planera i god tid innan kraven på antalet partitionstabeller.

Det kommer att felas när du försöker lägga till en ny partition med en annan återstod.

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

Hash-partitionering kan fungera på alla datatyper och det kan också fungera för UUID-typ. Det rekommenderas alltid att antalet tabeller ska vara en potens av 2, och det är inte heller obligatoriskt att använda samma modul när du skapar tabellen; detta hjälper till att skapa partitionstabellen senare vid behov.

Denna implementering skulle också göra vakuum snabbare och kan möjliggöra partitionsvis join.

Support för främmande nycklar

Före PostgreSQL 11 stöddes inte den främmande nyckeln i partitionstabellen. De främmande nycklarna är möjliga i partitionstabellen nu och nedan är hur...

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

Automatiskt indexskapande på underordnade tabeller

I tidigare versioner av PostgreSQL var det ett manuellt försök att skapa ett index på varje partitionstabell. I PostgreSQL version 11 är det ganska bekvämt för användare. När indexet har skapats på huvudtabellen kommer det automatiskt att skapa indexet med samma konfiguration på alla befintliga underordnade partitioner och ta hand om eventuella framtida partitionstabeller också.

Index skapat på huvudtabell

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

Det skapade automatiskt indexet på alla underordnade tabeller enligt nedan. (Verifiera med katalogtabell)

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 kan bara skapas på en huvudtabell, det kan inte vara på en underordnad tabell. Automatiskt genererade index kan inte raderas individuellt.

Skapa automatisk trigger på underordnade tabeller

När utlösaren har skapats på huvudtabellen kommer den automatiskt att skapa utlösaren på alla underordnade tabeller (detta beteende liknar det som visas för index).

Kan skapa ett unikt index

I version 11 kan unika index läggas till huvudtabellen som kommer att skapa den unika begränsningen för alla befintliga underordnade tabeller och framtida partitionstabeller.

Låt oss skapa en huvudtabell med unika begränsningar.

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

Den unika begränsningen har skapats på den underordnade tabellen automatiskt som nedan.

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)

Varning:En unik restriktion på den överordnade tabellen garanterar faktiskt inte unikhet över hela partitioneringshierarkin. Det är inte en global begränsning, den är endast lokal.

Ladda ner Whitepaper Today PostgreSQL Management &Automation med ClusterControlLäs om vad du behöver veta för att distribuera, övervaka, hantera och skala PostgreSQLDladda Whitepaper

Snabbare frågeprestanda

Dynamisk partitionsbeskärning

I PostgreSQL 11 möjliggör den binära sökningen snabbare identifiering av nödvändiga underordnade tabeller oavsett om det är LIST- eller RANGE-partitionerat. Hashing-funktionen hittar den matchande partitionen för HASH-partitionen. Det eliminerar faktiskt dynamiskt partitionstabellen/-tabellerna som inte krävs och ökar Query-prestandan.

Den dynamiska partitionsbeskärningen kan styras av parametern `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)

Den andra fantastiska implementeringen är så här.

Execution-Time Partition Beskärning

I PostgreSQL-versioner före 11 kan partitionsbeskärning endast ske vid planeringstiden; planerare kräver ett värde för partitionsnyckel för att identifiera rätt partition. Detta beteende är fixat i PostgreSQL 11, eftersom exekveringstidsplaneraren skulle veta vilket värde som levereras och baserat på att partitionsval / eliminering är möjligt och skulle köra mycket snabbare. Användningsfallet kan vara en fråga som använder parameter (prepared statement) ELLER subquery som tillhandahåller värdet som en parameter.

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)

I förklara planen ovan kan vi se, vid tidpunkten för exekvering, identifierade planeraren i farten den korrekta partitionstabellen baserat på parametervärde, och sprang mycket snabbare och spenderade inte tid på scan/loop på annan partitionstabell (se aldrig utfört avsnitt i förklara planen ovan). Detta är mycket kraftfullt och startade en ny era av prestandaförbättringar inom partitionering.

Partition Wise Aggregate

Parameter:enable_partitionwise_aggregate

Om partitionsnyckeln matchar grupperingsnyckeln kommer varje partition att producera en diskret uppsättning grupper istället för att skanna hela partitionen på en gång. Den kommer att göra parallellaggregeringen för varje partition och under det slutliga resultatet sammanfogar den alla resultat.

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)

Detta är säkerligen snabbare eftersom det inkluderar parallell aggregering och skanning per partition.

Katalogfråga kan användas för att känna till alla överordnade partitionstabeller.

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

Kort partitionsfunktionsmatris

Partitioneringsfunktioner v11 v10
Standardpartition JA NEJ
Utländskt tabellarv JA NEJ
Partitionering med Hash Key JA NEJ
Stöd för PK &FK JA NEJ
UPPDATERA på en partitionsnyckel JA NEJ
Automatiska index på CT JA NEJ
Automatiska utlösare på CT JA NEJ
Exekveringstidspartitionbeskärning JA NEJ
Gå med partitionsmässigt JA NEJ
Dynamisk partitionsutskärning JA NEJ

Vad är härnäst?

Partitioneringsprestanda

Detta är ett av de mest aktiva arbetsområdena nu i PostgreSQL-communityt. PostgreSQL version 12 kommer att paketeras med ännu fler prestandaförbättringar i partitioneringsutrymmet. Version 12 förväntas släppas i november 2019.


  1. SELECT DISTINCT Fungerar inte Android SQLite

  2. När du öppnar en oracle-anslutning är anslutningsobjektet null

  3. Hur man installerar SQL Server på en Mac med VirtualBox

  4. Hur beräknar jag en löpande summa i SQL utan att använda en markör?