資料庫筆記

ClickHouse

開源欄式 OLAP 資料庫,用 SQL 對大量資料做即時聚合分析。官方把「即時」定義為查詢結果在 1 秒內回傳。

1. ClickHouse 是什麼

ClickHouse 是一個開源(Apache 2.0)的欄式 OLAP 資料庫,使用 SQL 查詢,原始設計目標是對大量資料做即時聚合分析。官方將「即時」定義為查詢結果在 1 秒內回傳。

1.1 OLTP 與 OLAP

資料庫的工作負載大致分兩類,設計取向完全不同:

OLTP(如 PostgreSQL、MySQL) OLAP(如 ClickHouse)
典型操作 單筆或少量資料的讀寫、更新 掃描大量資料做彙總(COUNT、SUM、GROUP BY)
每次查詢讀取的資料 少數幾列 數百萬至數十億列,但只用少數幾欄
儲存方式 行式(同一列的欄位放一起) 欄式(同一欄的資料放一起)
交易 完整 ACID 有限
資料變動 頻繁更新、刪除 以新增為主,很少修改

1.2 為什麼欄式儲存適合分析

假設一張表有 50 個欄位,查詢只需要其中 2 欄:

代價是:要取回「一整列」或修改單列時,需要碰多個欄位檔案,成本較高。這也是它不適合 OLTP 的根本原因。

1.3 定位

2. 適合與不適合

2.1 適合的資料與場景

資料類型 為什麼適合
日誌、事件、追蹤(trace) 持續大量寫入、寫入後幾乎不修改、查詢以時間範圍+聚合為主
時序資料、指標 依時間排序與分割區,TTL 可自動過期清理
Clickstream、使用者行為 資料量大、寬表、多維度分析
即時儀表板、報表 秒級聚合查詢,可搭配 Materialized View 預先聚合

共同特徵:資料量大、以新增為主、查詢是掃描與聚合、欄位多但每次只用少數幾欄。

2.2 不適合的情境

情境 原因
頻繁 UPDATE / DELETE 單列 更新是背景 mutation,刪除只是標記,成本高
需要跨語句交易 無完整多語句交易(實驗性功能有嚴格限制)
以主鍵取單列為主 稀疏索引不是為單列點查設計
小量頻繁寫入(每筆一次 INSERT) 每次 INSERT 產生一個 part,太多 part 會拖慢合併與查詢;官方建議批次寫入
需要唯一性約束 主鍵不保證唯一

3. 為什麼快、特點

3.1 效能來源

  1. 欄式儲存 + 高壓縮:只讀需要的欄位;同欄資料壓縮後,磁碟讀取量更小。
  2. 稀疏主鍵索引:資料依主鍵排序後切成 granule(預設約 8192 列),索引只記錄每個 granule 的起點,因此索引小到可常駐記憶體,並能跳過不相關區塊。
  3. 向量化執行:以「一批列」為單位處理,而非逐列,配合 SIMD 指令加速。
  4. 多核與多節點平行:單一查詢可同時使用所有 CPU 核心,叢集可跨節點平行。
  5. 批次寫入設計:每次 INSERT 直接寫成不可變的 part,背景再合併,寫入路徑很輕。

3.2 功能特點

特點 說明
豐富的資料型態 數值、字串、日期時間、Array、Tuple、Map、JSON、LowCardinality、UUID、Enum 等
多種 JOIN 預設 hash join;另有 parallel_hash、grace_hash、full_sorting_merge、ASOF 等
TTL 資料過期可自動刪除、重新壓縮、搬到其他磁碟或做聚合;在背景合併時才評估,不是即時
分層儲存 熱資料放 SSD,冷資料搬到 HDD 或 S3
Materialized View INSERT 時增量更新,適合預先聚合
跳躍索引、Projection 針對非主鍵條件加速查詢
複製與分片 非同步多主複製,可選 quorum 同步寫入;大多數故障可自動恢復
近似計算 可用取樣或近似聚合函式,以精度換速度

3.3 要記住的取捨

ClickHouse 的快,來自放棄傳統資料庫的一些保證:不保證主鍵唯一、不即時更新、不提供完整交易。

4. 缺點與限制

項目 限制 對策
更新/刪除成本高 更新是背景 mutation;刪除只是標記 資料模型以「新增」為主
交易有限 無完整多語句交易;INSERT 預設不 fsync 確認寫入設定,不依賴 rollback
主鍵不唯一 主鍵只管排序與索引,重複 key 不報錯 不當唯一約束;用 FINAL 或應用層去重
去重/刪除語意 ReplacingMergeTree 只在合併時去重 Langfuse 的刪除列會留在表中
JOIN 限制多 無 JOIN 順序最佳化;預設不溢寫磁碟 先過濾,小表放右邊
小量頻繁寫入 每次 INSERT 一個 part,過多會拖慢合併 批次寫入或非同步 INSERT
維運複雜 叢集需 Keeper;shard/replica 自行規劃 自管成本高時考慮雲端版

細節補充

5. 核心概念

5.1 MergeTree 引擎家族

主力表引擎,建表時必須指定 ORDER BY(排序鍵,預設即主鍵):

CREATE TABLE events
(
    ts       DateTime,
    user_id  UInt64,
    action   LowCardinality(String),
    value    Float64
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (user_id, ts);

5.2 Part 與 Merge

  1. 每次 INSERT 寫成一個不可變的 part(含排序後的各欄資料)。
  2. 背景執行緒把小 part 合併成大 part。
  3. Part 有兩種格式:Wide(每欄一個檔案)與 Compact(所有欄位一個檔案,利於小量寫入)。
  4. TTL、ReplacingMergeTree 的去重、刪除標記的實際清除,都是在合併時才發生。這就是為什麼它們「不即時」。

5.3 ReplacingMergeTree:用新增模擬更新

做法是:要更新時,插入一筆同 key、版本更高的新列;合併時保留版本最大的那列。

ENGINE = ReplacingMergeTree(event_ts, is_deleted)
ORDER BY (project_id, id)

5.4 複製、分片與 Keeper

概念 說明
Replica(副本) 同一份資料的多個複本,提供高可用;複製為非同步
Shard(分片) 把資料水平切到多個節點,擴充容量與吞吐
Keeper 負責協調的服務(Raft,通常 3 個),保存複製的全域 log;也用於 insert 去重
Distributed 表 跨 shard 查詢的入口表;本身不存資料

5.5 加速查詢的輔助機制

機制 用途 注意
跳躍索引(minmax、set、bloom_filter、text) 針對非主鍵條件,跳過整個區塊 不是 B-tree;要與排序鍵有相關性才有效,官方建議最後才考慮
Projection 以不同排序或預聚合存一份,查詢自動選用 不支援帶 FINAL 的 SELECT
Materialized View INSERT 時增量更新到另一張表 只處理新寫入的資料
欄位統計 協助 PREWHERE 最佳化 需 use_statistics=1;部分類型建立成本高

6. 跟一般 SQL 差多少

基本查詢(SELECT / WHERE / GROUP BY / JOIN / 視窗函式)與標準 SQL 差不多,差異主要在寫入與建表:

項目 一般 SQL ClickHouse
建表 CREATE TABLE 即可 必須指定 ENGINE 與 ORDER BY
更新 UPDATE ... SET ALTER TABLE ... UPDATE(背景 mutation)
刪除 立即生效 標記刪除,合併後才實際移除
交易 BEGIN / COMMIT 無完整多語句交易
主鍵 保證唯一 排序/稀疏索引,不保證唯一
NULL 欄位預設可為 NULL 需明確寫 Nullable(T),官方指出幾乎總是影響效能
函式名 不分大小寫 多數函式名區分大小寫
額外語法 — PREWHERE、FINAL、ARRAY JOIN、LIMIT BY、ASOF JOIN、SETTINGS