OLAP Anti-Patterns
部分內容由 LLM 生成,尚未經過人工驗證。
OLAP(Online Analytical Processing)系統的常見反模式與最佳實踐。
Schema 設計
| 反模式 | 問題 | 最佳實踐 |
|---|---|---|
| 過度正規化 | 分析查詢需要大量 JOIN,效能差 | 使用星型或雪花模型(適度反正規化) |
| 過度雪花模型 | 維度表切分過細,查詢複雜度增加 | 平衡正規化與查詢效能,考慮扁平化維度 |
星型 vs 雪花模型
星型模型(Star Schema):
┌─────────────┐
│ dim_time │
└──────┬──────┘
│
┌───────────┐ │ ┌────────────┐
│dim_product├──┼──┤dim_customer│
└───────────┘ │ └────────────┘
│
┌──────┴──────┐
│ fact_sales │
└─────────────┘- 維度表直接連接事實表
- 查詢簡單,JOIN 少
- 適合大多數分析場景
雪花模型(Snowflake Schema):
┌──────────────┐
│ dim_category │
└──────┬───────┘
│
┌──────┴───────┐ ┌─────────────┐
│ dim_product ├────┤ fact_sales │
└──────────────┘ └─────────────┘- 維度表進一步正規化
- 減少資料冗餘
- 查詢需要更多 JOIN
ETL/查詢優化
| 反模式 | 問題 | 最佳實踐 |
|---|---|---|
| 全量重建 | 每次 ETL 都重建整個資料表,耗時耗資源 | 使用增量載入(incremental load) |
| 過度依賴視圖 | 多層嵌套視圖,執行計畫難以優化 | 使用物化視圖或預計算表 |
| 查詢效率差 | 未利用分區、索引、資料分佈特性 | 善用分區剪枝、列式儲存優勢 |
| 過度即席查詢 | 使用者直接跑複雜查詢影響系統 | 提供預建報表或 BI 工具層 |
| 聚合邏輯在查詢中 | 每次查詢都重新計算聚合 | 預先計算聚合存入摘要表 |
增量載入策略
反模式(全量重建):
TRUNCATE TABLE fact_sales;
INSERT INTO fact_sales SELECT * FROM staging_sales;最佳實踐(增量載入):
-- 使用時間戳或 CDC 識別變更
MERGE INTO fact_sales AS target
USING (
SELECT * FROM staging_sales
WHERE updated_at > @last_load_time
) AS source
ON target.id = source.id
WHEN MATCHED THEN UPDATE SET ...
WHEN NOT MATCHED THEN INSERT ...;增量載入類型:
| 類型 | 適用場景 |
|---|---|
| Append Only | 只有新增資料(如日誌) |
| Upsert (MERGE) | 有新增和更新 |
| CDC | 需要追蹤刪除 |
| SCD Type 2 | 需保留歷史變更 |
物化視圖使用
反模式(多層嵌套視圖):
CREATE VIEW v1 AS SELECT ... FROM base_table;
CREATE VIEW v2 AS SELECT ... FROM v1 WHERE ...;
CREATE VIEW v3 AS SELECT ... FROM v2 JOIN ...;
-- 查詢 v3 時優化器難以產生好的執行計畫最佳實踐(物化視圖):
CREATE MATERIALIZED VIEW mv_daily_sales AS
SELECT
date_trunc('day', sale_date) AS day,
product_id,
SUM(amount) AS total_amount,
COUNT(*) AS transaction_count
FROM fact_sales
GROUP BY 1, 2;
-- 定期刷新
REFRESH MATERIALIZED VIEW mv_daily_sales;分區策略
常見分區方式:
| 分區類型 | 適用場景 | 範例 |
|---|---|---|
| Range | 時間序列資料 | 按月/年分區 |
| List | 離散值 | 按地區/類別 |
| Hash | 均勻分佈 | 按 ID hash |
分區剪枝範例:
-- 建立分區表
CREATE TABLE fact_sales (
id BIGINT,
sale_date DATE,
amount DECIMAL(10,2)
) PARTITION BY RANGE (sale_date);
-- 查詢時自動剪枝
SELECT SUM(amount)
FROM fact_sales
WHERE sale_date BETWEEN '2024-01-01' AND '2024-01-31';
-- 只掃描 2024-01 分區預計算聚合表
反模式(每次即時計算):
-- 儀表板每次載入都執行
SELECT
region,
product_category,
SUM(amount) AS total_sales
FROM fact_sales
WHERE sale_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY region, product_category;最佳實踐(預計算):
-- 每日 ETL 任務預先計算
CREATE TABLE agg_daily_sales AS
SELECT
sale_date,
region,
product_category,
SUM(amount) AS total_sales,
COUNT(*) AS num_transactions
FROM fact_sales
GROUP BY sale_date, region, product_category;
-- 儀表板查詢預計算表
SELECT region, product_category, SUM(total_sales)
FROM agg_daily_sales
WHERE sale_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY region, product_category;