資料庫分割 (Partitioning) 指南

隨著應用程式的成功,資料量也跟著水漲船高,這本是件甜蜜的負擔。但很快地,會發現一張核心資料表(例如 orderslogs)變得異常肥大,查詢速度越來越慢,甚至連簡單的維護操作都變得舉步維艱。這時可能就需要祭出資料庫領域的「空間整理大師」——資料庫分割 (Database Partitioning)


什麼是資料庫分割?為什麼需要它?

想像一下,有一間巨大的圖書館,裡面所有的書都隨機擺放在同一個書架上。當要找一本書時,必須從頭到尾掃描整個書架,耗時又費力。

資料庫分割 (Partitioning),就是把這個巨大的書架,按照某種規則(例如:按年份、按書籍類別)分成數個獨立的小書架。雖然從外面看,它仍然是「一個圖書館」,但當要找一本 2023 年出版的書時,你只需要去標示著「2023年」的小書架尋找即可,大大縮短了搜尋時間。

在技術上,Partitioning 是指將一個龐大的資料表(或索引)在邏輯上仍然視為單一表格,但在物理上將其資料儲存到多個較小的、可獨立管理的片段(Partitions)中

採用資料庫分割主要有三大好處:

  1. 查詢效能提升 (Query Performance):當查詢條件包含分割鍵(Partition Key)時,資料庫優化器可以聰明地只掃描相關的分割區,這個過程稱為分割區裁剪 (Partition Pruning)。掃描 1000 萬筆資料,遠比掃描 10 億筆資料來得快。同時,更小的索引也能更快地被載入記憶體。

  2. 管理效率提升 (Manageability):對於時間序列資料(如日誌、訂單記錄),管理變得異常輕鬆。需要封存或刪除三年前的舊資料?不再需要執行 DELETE ... WHERE date < '...' 這種會產生大量 I/O 和交易日誌的昂貴操作,而是可以直接 DROPDETACH 整個舊的分割區,操作幾乎在瞬間完成。

  3. 可用性提升 (Availability):如果某個分割區因為硬體故障或索引損壞而出現問題,在某些情況下,其他分割區可能仍然可以正常存取,提升了整體的可用性。

分割的類型:常見的分割策略

選擇哪種分割策略,取決於資料特性和查詢模式。以下是最常見的三種分割方法:

1. 範圍分割 (Range Partitioning)

這是最直觀也最常用的分割方式,它根據一個連續性的值(通常是數字或日期)來劃分資料。

  • 應用場景:非常適合時間序列資料,如訂單記錄、日誌檔、交易資料等。
  • 範例:將一張 orders 表格按照 order_date 每季一個分割區。
    • orders_q1 分割區:儲存 1 月到 3 月的訂單。
    • orders_q2 分割區:儲存 4 月到 6 月的訂單。
    • 以此類推…

2. 列表分割 (List Partitioning)

列表分割是根據欄位中一組不連續的、離散的值來劃分資料。

  • 應用場景:適合欄位值是可枚舉的類別資料,如地區、國家、部門、狀態碼等。
  • 範例:將 users 表格按照所在地區 region 進行分割。
    • users_north 分割區:儲存 region IN ('Taipei', 'Hsinchu') 的使用者。
    • users_south 分割區:儲存 region IN ('Tainan', 'Kaohsiung') 的使用者。
    • users_others 分割區:儲存其他地區的使用者。

3. 雜湊分割 (Hash Partitioning)

當資料沒有明顯的範圍或類別可供劃分時,雜湊分割是個好選擇。它會根據分割鍵的雜湊值,將資料均勻地分配到各個分割區。

  • 應用場景:希望將資料均勻打散,避免資料熱點,適合透過唯一 ID 查詢的場景。
  • 範例:將 products 表格按照 product_id 進行雜湊分割,以確保資料能平均分布在 4 個分割區中。資料庫會對 product_id 進行雜湊運算,再根據結果決定該筆資料應存入哪個分割區。

進階技巧:許多資料庫還支援複合分割 (Composite Partitioning),例如先按 order_date 進行範圍分割,然後在每個月份的分割區內,再按 customer_id 進行雜湊分割,實現更精細的資料管理。

如何應用?一個 SQL 實戰範例

以 PostgreSQL 為例,展示如何建立一個按範圍分割的 access_logs 表。

步驟 1:建立主表 (Partitioned Table)

首先,建立一個「樣板」主表,並宣告它將以 log_date 進行範圍分割。

CREATE TABLE access_logs (
    log_id BIGINT GENERATED ALWAYS AS IDENTITY,
    ip_address VARCHAR(45) NOT NULL,
    log_date DATE NOT NULL,
    url TEXT NOT NULL
) PARTITION BY RANGE (log_date);

此時,access_logs 表本身不能儲存任何資料,它只是一個邏輯上的容器。

步驟 2:建立分割區 (Partitions)

接下來,為不同的日期範圍建立實際儲存資料的子表(分割區)。

-- 建立 2023 年 Q1 的分割區
CREATE TABLE access_logs_2023_q1 
PARTITION OF access_logs 
FOR VALUES FROM ('2023-01-01') TO ('2023-04-01');

-- 建立 2023 年 Q2 的分割區
CREATE TABLE access_logs_2023_q2 
PARTITION OF access_logs
FOR VALUES FROM ('2023-04-01') TO ('2023-07-01');

步驟 3:插入與查詢

現在,可以像操作普通表格一樣對 access_logs 進行操作。資料庫會自動將資料路由到正確的分割區。

-- 插入資料,這筆資料會自動進入 access_logs_2023_q1
INSERT INTO access_logs (ip_address, log_date, url) 
VALUES ('192.168.1.100', '2023-02-15', '/api/products');

神奇之處在於查詢:

-- 查詢特定日期的日誌
EXPLAIN SELECT * FROM access_logs WHERE log_date = '2023-02-15';

會在查詢計畫 (Query Plan) 中看到,資料庫只會去掃描 access_logs_2023_q1 這一個子表,完全忽略了其他分割區。這就是分割區裁剪的威力!

優缺點大比拼:何時該用哪種方法?

分割類型 主要應用場景 優點 缺點
範圍分割 (Range) 時間序列資料(日誌、訂單)、連續性數值資料。 1. 非常適合基於時間範圍的查詢。
2. 封存/刪除舊資料極其高效(直接操作分割區)。
可能產生「熱點」分割區,例如所有新的寫入都集中在最新的分割區。
列表分割 (List) 類別資料(地區、部門、狀態碼),且類別數量有限。 1. 資料分佈符合業務邏輯,易於理解。
2. 針對特定類別的查詢非常快。
如果需要頻繁增減新的類別,維護成本較高(需手動新增分割區)。
雜湊分割 (Hash) 沒有自然的分割鍵、希望均勻分佈資料以避免熱點。 1. 資料分佈最均勻。
2. 非常適合負載均衡與平行處理。
1. 範圍查詢效能差(需掃描所有分割區)。
2. 缺乏業務邏輯上的直觀性。

結論:分割是一把雙面刃

資料庫分割是一個強大的效能優化和資料管理工具,但它並非萬靈丹。錯誤的分割策略不僅沒有好處,反而會增加管理的複雜度。

在決定採用分割之前,請務必問自己幾個問題:

  • 痛點是什麼? 是查詢慢,還是資料管理困難?
  • 主要查詢模式是什麼? 是按時間範圍查,還是按地區查?
  • 資料增長模式是怎樣的?

分割的精髓在於,讓資料物理儲存結構,與最頻繁的資料存取模式保持一致。