設計並實作資料分割方案
資料分割直接影響查詢的效率以及你消耗的運算資源。 當你劃分資料表時,你會根據欄位值將資料分割成離散區段,讓查詢引擎只讀取資料集中相關的部分。 這種稱為 分割剪枝的方法,當過濾器與分割區邊界對齊時,能大幅減少資料掃描次數。
想像一家零售公司每天處理數百萬筆銷售交易。 分析師經常依日期範圍查詢資料,以產生每週報告、比較月比趨勢,並分析季節性模式。 若不分割,每次查詢都會掃描整個交易歷史。 透過正確的日期分割,特定一週的查詢只會觸及儲存檔案的一小部分。
判斷何時適用分割
Databricks 建議不要分割包含少 於 1 TB 的資料表。 對於較小的資料表,管理分割區元資料的負擔以及可能產生許多小檔案的負擔,遠超過查詢效能的提升。 近期 Databricks 執行時版本中的未分割 Delta 資料表會自動受益於 擷取時間叢集,這在不需明確設定的情況下也能提供類似的查詢優勢。
當你的資料表超過 1 TB 時,分割就成為一項有價值的優化技術。 在此規模下,僅讀取相關資料區段的好處顯著抵銷分割區管理成本。 分割也促進平行處理,因為 Spark 能將不同分割區分散到叢集節點以進行並行執行。
這很重要
每個分割區至少應包含 1 GB 的資料。 此指引有助於避免過度分割,避免造成過小檔案並降低查詢效能。
對於數百TB級的超大型資料表,分割帶來更大的好處。 在此規模下,分割剪枝大幅減少查詢引擎所需處理的資料量,提升效能與成本效益。
選擇有效的分割鍵
分割區鍵選擇需要你了解資料特性與查詢模式。 有效的分割鍵具有共同特性,能最大化效能效益,同時避免常見陷阱。
低基數欄位效果最佳
選擇具有 有限數量不同值的欄位。 日期欄位、地理區域、產品類別和狀態欄位是優秀的分割鍵,因為它們能產生可管理的分割區數量。 包含數千甚至數百萬個獨特值的欄位,如客戶 ID 或交易 ID,會導致過度分割和效能不佳。
例如,將銷售表 sale_date 以每日層級分割,每年可能產生 365 個分割區。 如果每天的資料量足夠,這種細緻度就很有效。 然而,若在第二層級分割, sale_timestamp 將產生數百萬個分割區,每個分割區都包含極少的資料。
與查詢模式對齊
你的分割鍵應該與使用者最常過濾的欄位相符。 如果分析師持續依日期範圍查詢銷售數據,依日期劃分有助於有效修剪。 如果查詢主要依區域篩選,請將區域視為你的分割鍵。
當存在多種過濾模式時,優先處理最常見或對效能最關鍵的查詢。 你可以為每個資料表定義一種分割方案,因此選擇能帶來大部分工作負載的欄位。 對於差異顯著的查詢模式,可以考慮為特定使用情境建立獨立的 優化化實體化檢視 表或 摘要表 。
考量資料隨時間的成長
選擇一個能隨著資料成長適當擴展的分割方案。 以年份劃分初期效果良好,但隨著時間推移可能會產生過大的分割區。 相反地,按小時分割可能適用於高容量資料,但對於中等容量會產生過多小分割區。
在選擇分割粒度時,評估你預期的資料成長率。 在分割區大小與分割區數量之間取得平衡,有助於在資料集擴展時維持查詢效能的一致性。
也要考慮數據在不同分區間的分布。 嚴重不均的分割區大小會造成處理瓶頸,因為處理較大分割區的節點完成時間較長,而其他節點則處於閒置狀態。
在 Azure Databricks 中實施分區
你在建立資料表時,使用 PARTITIONED BY SQL 的子句或 partitionBy PySpark 的方法來定義分割區。 一旦建立分割區,Azure Databricks 會根據欄位值自動將輸入資料導向適當的分割目錄。
建立一個帶有 SQL 的分割表格
以下範例建立一個以交易日期劃分的銷售交易表:
CREATE TABLE sales.transactions (
transaction_id STRING,
customer_id STRING,
product_id STRING,
quantity INT,
unit_price DECIMAL(10,2),
total_amount DECIMAL(12,2),
transaction_date DATE
)
PARTITIONED BY (transaction_date);
當你用日期篩選器查詢此表格時,查詢引擎只會讀取符合你篩選條件的分割區:
SELECT product_id, SUM(total_amount) AS revenue
FROM sales.transactions
WHERE transaction_date BETWEEN '2024-01-01' AND '2024-01-31'
GROUP BY product_id;
用 PySpark 建立分割表格
使用 PySpark 時,請在寫入操作中指定分割:
df.write.format("delta") \
.partitionBy("transaction_date") \
.saveAsTable("sales.transactions")
後續寫入時,請透過包含相同的分割規範來維持一致性:
new_transactions_df.write.format("delta") \
.mode("append") \
.partitionBy("transaction_date") \
.saveAsTable("sales.transactions")
使用生成欄位來增強分割靈活性
產生的欄位提供一種從現有欄位中推導分割值的方法,且不儲存冗餘資料。 當資料來源包含時間戳記,但你想依日期劃分時,這種方法非常有用:
CREATE TABLE sales.transactions (
transaction_id STRING,
customer_id STRING,
product_id STRING,
quantity INT,
unit_price DECIMAL(10,2),
total_amount DECIMAL(12,2),
transaction_timestamp TIMESTAMP,
transaction_date DATE GENERATED ALWAYS AS (CAST(transaction_timestamp AS DATE))
)
PARTITIONED BY (transaction_date);
小提示
透過這種設定,Delta Lake 在使用基礎時間戳欄查詢時會自動產生分割區過濾器。 對 transaction_timestamp 進行查詢篩選,不需要明確的日期篩選,即可受益於分割區剪除。
避免常見的分割錯誤
了解該避免什麼,和知道該怎麼做一樣重要。 幾種常見錯誤會大幅降低效能並複雜化資料管理。
過度分割會造成小型檔案問題
建立過多分割區,每個分割區資料不足,會導致 檔案出現小問題。 當分割區中檔案大小小於目標檔案時,查詢效能會因過度的元資料操作而受損,且讀取效率降低。 目標是擁有至少 1 GB 資料的分割區。
過度分割的跡象包括即使分割區修剪後查詢效能仍緩慢、分割區目錄中有大量小檔案,以及表格優化時間過長。 如果你觀察到這些症狀,建議考慮選擇較粗的分區粒度。
高基數資料行的資料分割失敗
擁有許多獨特值的欄位會成為不良的分割鍵。 依客戶 ID、交易 ID 或其他 高基數欄位分割,會 產生數百萬個分割區,這些分割區不僅沒有提升效能,反而會降低效能。 將高基數資料行最佳化保留給其他專為此目的設計的技術。
更改分割區需要重寫資料
分割欄位是資料表結構的一部分。 改變分割策略需要 將整個資料表重寫 到不同分割區邊界的新位置。 此操作對大型資料表而言會消耗大量運算資源與時間。
在大規模載入資料前,請仔細規劃你的分割策略。 考慮未來的查詢模式與資料成長,以避免日後昂貴的重組。
文件分割決策
記錄你的分割鍵選擇及其背後的原因。 這些文件幫助團隊成員了解查詢優化的機會,並防止可能影響效能的意外變更。 包含預期分割區大小、受益於該方案的查詢模式,以及影響決策的限制因素等資訊。
考慮液態聚類作為替代方案
當你遇到分割困難時——尤其是像時間戳這 類高基數欄位 ,或查詢模式 不確定時——液態聚類提供了現代化的替代方案。 與傳統分割不同,液體叢集提供:
- 彈性調整優化策略而無需重寫資料
- 隨著資料表的增長,自動進行維護
- 減少分割區管理開銷
液態叢集是新資料表 中建議的方法 ,因為分割限制會限制效能或造成過度維護負擔。 它對於超過 1 TB 的資料表特別有效,且需要對傳統方法會產生過多分割區的欄位進行過濾。
備註
液態叢集與分割資料表不相容。 設計新表格時,請根據需求選擇一種方法。
關於實作細節、語法及優化策略,請參閱本模組後面的 「設計與實作分群策略 單元」。