PostgreSQL 中的每項特性,總是不急不慢地在慢慢演進當中,單看每個版本可能進步不大,拉長時間來看,總會讓你大吃一驚。比如:邏輯復制、并行查詢,還有表分區。
PostgreSQL 表分區命令
在版本 17 以前,分區管理流程的操作僅限于創建、依附和分離分區。一旦我們設計了分區結構,我們就無法重新設計它。這適用于所有分區類型,無論我們使用的是RANGE
、LIST
還是HASH
。
要將多個分區合并為一個分區,或者將單個分區“拆分”為多個分區,我們需要設計一個新的分區結構,然后將所有數據行遷移到該結構。這需要很多步驟!
從版本 17 開始,我們有了更多的選擇。現在,我們可以對現有的單個分區執行SPLIT PARTITION
操作,將其分成兩個或多個新分區。
如果我們想做相反的事情,我們也同樣可以。從兩個或多個分區開始,我們可以執行MERGE PARTITIONS
的操作,將它們合并為一個。
合并分區
下表有一個account_id
列,但沒有實際的數據列,因為我們只是想演示分區管理方面的操作。
id
使用了生成的序列值,這意味著每一行在分區中都會有一個唯一的值。
CREATE TABLE t (
id INT GENERATED ALWAYS AS IDENTITY,
account_id INT NOT NULL
) PARTITION BY LIST (account_id);
假設我們有以下兩個分區,分別對應account_id
為 1 和account_id
為 2。
CREATE TABLE t_account_1 PARTITION OF t FOR VALUES IN (1);
CREATE TABLE t_account_2 PARTITION OF t FOR VALUES IN (2);
讓我們為account_id
為 1 插入 10 條記錄,為account_id
為 2 插入 100 條記錄。我們總共會有 110 條記錄,但它們分布在兩個分區中。我們想把它們合并在一起。
INSERT INTO t (account_id) SELECT 1 FROM GENERATE_SERIES(1,10);
INSERT INTO t (account_id) SELECT 2 FROM GENERATE_SERIES(1,100);
現在我們使用MERGE PARTITIONS
將它們合并在一起:
ALTER TABLE t
MERGE PARTITIONS (t_account_1, t_account_2)
INTO t_account_1_2;
它將t_account_1
和t_account_1
合并為了一個分區,名為t_account_1_2
,含有 110 條記錄。
那拆分分區呢?它又是如何工作的?
拆分分區
我們已經了解了如何合并分區。我們還可以使用SPLIT PARTITIONS
命令來拆分分區。
在此示例中,我們使用RANGE
分區類型。
想象一下,我們決定為接收大量記錄的一個“事件”類的表,創建一周數據的分區。我們在下面把表叫做t_events
。
我們決定了以一周為限,但是發現表的數據量很大。我們希望改用每日分區,以讓一天數據的表更小、更易于管理。
讓我們看一下 SQL 命令,了解如何實現這一點。
拆分分區事件表
使用RANGE
分區類型創建t_events
表,最初使用每周分區來演示當前的配置。
CREATE TABLE t_events (
id INT GENERATED ALWAYS AS IDENTITY,
event_at TIMESTAMP WITHOUT TIME ZONE NOT NULL
) PARTITION BY RANGE (event_at);
以下是“上周”、“本周”和“下周”的分區。
CREATE TABLE t_events_last_week PARTITIONOF t_events
FORVALUESFROM('2024-04-08 00:00:00')TO('2024-04-15 00:00:00');
CREATETABLE t_events_this_week PARTITIONOF t_events
FORVALUESFROM('2024-04-15 00:00:00')TO('2024-04-22 00:00:00');
CREATETABLE t_events_next_week PARTITIONOF t_events
FORVALUESFROM('2024-04-22 00:00:00')TO('2024-04-29 00:00:00');
現在我們想對叫做t_events_next_week
的“下周的分區”,把它分成 7 個每日分區,每天一個。
由于這是即將到來的一周,我們假設它沒有數據,但是一個預先創建的分區。
在像這樣設計自己的更改時,請記住,您提出的最終邊界必須具有與當前配置等效的開始和結束邊界。
如果邊界對應不上,您會收到如下錯誤:
ERROR: partition bound for relation "t_events_next_week" is null
下面是SPLIT PARTITION
的 DDL 命令,用于將單周的分區拆分為 7 個每日分區:
ALTER TABLE t_events SPLIT PARTITION t_events_next_week INTO(
PARTITION t_events_day_1 FORVALUESFROM('2024-04-22 00:00:00')TO('2024-04-23 00:00:00'),
PARTITION t_events_day_2 FORVALUESFROM('2024-04-23 00:00:00')TO('2024-04-24 00:00:00'),
PARTITION t_events_day_3 FORVALUESFROM('2024-04-24 00:00:00')TO('2024-04-25 00:00:00'),
PARTITION t_events_day_4 FORVALUESFROM('2024-04-25 00:00:00')TO('2024-04-26 00:00:00'),
PARTITION t_events_day_5 FORVALUESFROM('2024-04-26 00:00:00')TO('2024-04-27 00:00:00'),
PARTITION t_events_day_6 FORVALUESFROM('2024-04-27 00:00:00')TO('2024-04-28 00:00:00'),
PARTITION t_events_day_7 FORVALUESFROM('2024-04-28 00:00:00')TO('2024-04-29 00:00:00')
);
現在,如果我們運行\d+ t_events
來查看t_events
,我們將看到剩下的兩個每周分區,以及新的 7 個每日分區。
有一個問題。執行此操作需要父表上的鎖,該鎖的持有時間可能會很長。
有解決方法嗎?
分離、拆分、重新關聯
只要表的結構保持不變,分區就可以分離和重新關聯。
這些操作都可以通過使用CONCURRENTLY
,以非阻塞的方式執行。
不幸的是,我們無法執行SPLIT PARTITION CONCURRENTLY
,這會使這更加方便,因為我們不必擔心在排他鎖生效時阻止寫入。
讓我們來考慮一種解決方法。我們知道我們可以分離分區,在分離時拆分它們,然后重新關聯它們。那行得通嗎?
這需要很多操作,并且需要一個“新的假父表”(下面有它自己的名字)才能工作,因此這些步驟應該更多地被視為概念證明,而不是建議。目標是通過允許在分離的表層次結構上發生鎖,來避免潛在的長鎖阻塞寫入。實質是“離線”的。
在沒有父表的已分離的分區上,嘗試運行SPLIT PARTITION
是無法工作的。但是,我們可以臨時添加一個“新的假父表”來代替。
下面是分離操作:
ALTER TABLE t_events
DETACH PARTITION t_events_next_week CONCURRENTLY;
下面是“假的”替身父表的定義。在創建它后,我們需要將已分離的分區關聯到它,以執行拆分。
我們只會使用“假父表”進行拆分操作。完成后,我們將再次分離分區,然后以CONCURRENTLY
的方式,將它們重新關聯到原始父表。到這一步后,我們可以刪除“假”父表了。
CREATE TABLE t_events_fake_new (
id INT GENERATED ALWAYS AS IDENTITY,
event_at TIMESTAMP WITHOUT TIME ZONE NOT NULL
) PARTITION BY RANGE (event_at);
在單獨的父表上運行SPLIT PARTITION
,可以避免在原始父表上長時間持有鎖,因為它是一個完全獨立的表。
ALTER TABLE t_events_fake_new SPLIT PARTITION t_events_next_week INTO(
PARTITION t_events_day_1 FORVALUESFROM('2024-04-22 00:00:00')TO('2024-04-23 00:00:00'),
PARTITION t_events_day_2 FORVALUESFROM('2024-04-23 00:00:00')TO('2024-04-24 00:00:00'),
PARTITION t_events_day_3 FORVALUESFROM('2024-04-24 00:00:00')TO('2024-04-25 00:00:00'),
PARTITION t_events_day_4 FORVALUESFROM('2024-04-25 00:00:00')TO('2024-04-26 00:00:00'),
PARTITION t_events_day_5 FORVALUESFROM('2024-04-26 00:00:00')TO('2024-04-27 00:00:00'),
PARTITION t_events_day_6 FORVALUESFROM('2024-04-27 00:00:00')TO('2024-04-28 00:00:00'),
PARTITION t_events_day_7 FORVALUESFROM('2024-04-28 00:00:00')TO('2024-04-29 00:00:00')
);
由于表結構沒有更改,并且由于我們沒有引入任何重疊的分區約束,因此我們可以重新關聯到原始父表。
替代方法
那么簡單地創建新分區,以將數據行移動到其中,又會怎么樣?
雖然創建新分區和移動數據行的工作量可能較少,但我們不能引入與任何現有分區的邊界/約束重疊的新分區。PostgreSQL 會強制執行該規則的檢查,并會阻止分區的創建。
為了避免重疊的限制,當我們的目標是像這樣就地修改結構時,執行SPLIT PARTITION
似乎是必要的。
但是,與上述解決方法類似,我們可以遵循相同的策略,分離重疊的分區以解決沖突。
使用這種方法,我們可以獲得相同的最終結果,而不需要執行SPLIT PARTITION
命令。
該文章在 2024/7/18 12:10:26 編輯過