# ClickHouse 是什麼?一個新手的學習與實作紀錄 ### 引言 近期在工作上開始需要接觸 **ClickHouse**,但對我來說這是一個完全陌生的資料庫系統。過去大多使用的是 Relational Database (關聯式資料庫),對於 ClickHouse 這類主打高效能分析查詢的 OLAP(線上分析處理)Database,其實並沒有太多實務經驗。 因此撰寫這篇文章,一方面是作為自己學習 ClickHouse 的紀錄,另一方面也希望透過「寫出來」的方式,幫助自己更好地理解與內化相關概念。 本文內容將以新手角度出發,著重在 ClickHouse 的基本概念、使用情境與實際操作體驗,不會深入過於底層或艱澀的實作細節。如果你也正準備第一次接觸 ClickHouse,希望這篇文章能成為一個友善的入門起點。 ### Why? Why we use this cool stuff? 這邊不免俗的先說一下官方對這個產品的應用場景 1. 實時分析 2. 可觀察性 3. 作為資料庫 4. 機器學習和生成式 AI 簡單來說,ClickHouse 最擅長處理的是 **需要高速分析和查詢大規模資料** 的場景 #### 那他的大規模是多大呢? 他的官方文件在 Abstract 的地方提及他可以處理 PB 級 (Petabyte, 千兆 byte) 的資料量 > This paper presents an overview of ClickHouse, a popular opensource OLAP database designed for high-performance analytics over petabyte-scale data sets with high ingestion rates. > [ClickHouse Architecture Overview](https://clickhouse.com/docs/academic_overview?utm_source=chatgpt.com) > 取自 Abstract ### What? What is ClickHouse? 在我們進入主題之前,必須先釐清兩個經常被一起提到、但用途完全不同的概念: OLTP 與 OLAP。 #### What is OLTP? > OLTP > Online Transaction Precessing, 線上交易處理 是我們日常開發中最熟悉的資料庫使用情境 他的特點是: - 高頻率、短時間的操作 - 以 新增(INSERT)/ 更新(UPDATE)/ 刪除(DELETE) 為主 - 通常只影響少量資料 - 強調交易正確性與一致性(ACID) 像 MySQL、PostgreSQL、MSSQL 這類傳統的 Relational DB,大多都是針對 OLTP 場景進行最佳化設計 > Relational DB 可以同時支援 OLTP 和 OLAP,只是通常來說是 OLTP #### What is OLAP? > OLAP > Online Analytical Processing, 線上分析處理 > 它描述的是一種針對大量資料做複雜查詢與分析的方式,而不是一般的交易式查詢。 OLAP 關心的不是「單筆資料是否成功寫入」,而是: - 對 大量歷史資料 進行查詢 - 複雜的聚合、統計與分群分析 - 查詢時會掃描大量資料,但寫入相對較少 - 目的是產生報表、洞察趨勢或分析結果 #### OLTP V.S. OLAP > OLTP 關心的是「一筆資料快不快、準不準」 > OLAP 關心的是「一大堆資料算得快不快」 雖然傳統 relational database 理論上可以同時支援 OLTP 與 OLAP,但在實務上: - 當資料量變大 - 查詢變得複雜(大量 GROUP BY / 聚合) - 分析需求頻繁出現 OLAP 查詢往往會: - 變得非常慢 - 影響原本的交易系統效能 | 項目 | OLTP | OLAP | |---|---|---| | 主要目的 | 處理日常交易與即時操作 | 分析大量歷史資料、產生報表與洞察 | | 常見操作 | INSERT / UPDATE / DELETE | SELECT(大量聚合、統計、分群) | | 查詢特性 | 短、快速、影響資料量小 | 複雜、耗資源、掃描大量資料 | | 資料量 | 相對較小(單筆或少量資料) | 非常大(百萬、億級甚至以上) | | 寫入頻率 | 高頻率寫入 | 寫入較少,讀取為主 | | 查詢延遲需求 | 極低(毫秒等級) | 可接受較高延遲,但仍希望快 | | 資料結構 | 高度正規化(Normalization) | 常為反正規化(Denormalization) | | 儲存方式 | 多為行式儲存(Row-oriented) | 多為列式儲存(Column-oriented) | | 典型使用情境 | 使用者登入、下單、付款 | 報表、趨勢分析、指標計算 | | 常見資料庫 | MySQL、PostgreSQL、Oracle | ClickHouse、BigQuery、Redshift | > 也就是說,當我們的需求從「穩定處理交易」轉向「快速分析大量資料」時, 就需要一種在設計上就為 OLAP 最佳化的資料庫。 > > ClickHouse,正是在這樣的背景下誕生的。 #### What is ClickHouse? 在了解 OLAP 的概念之後,其實 ClickHouse 是什麼也就呼之欲出了。 **ClickHouse 就是一個專門用來「快速分析大量資料」的資料庫。** 當你的需求是: - 對大量歷史資料做統計與分析 - 常常需要 `GROUP BY`、聚合、指標查詢 - 查詢效能開始成為瓶頸 那麼 ClickHouse,通常就是一個很合理的選擇。 ClickHouse 並不是要取代原本的 relational database, 而是用來處理 **OLAP(分析型)** 的需求,讓交易系統可以專心做好 OLTP。 ### How? How can I connect to ClickHouse? > 本篇文章是以 C# 作為範例,也可以去看看官方文件其他語言是怎麼連的 > [Language Client](https://clickhouse.com/docs/integrations/language-clients) 首先我們要在 proj 裡新增 ClickHouse.Driver 這個 package ``` dotnet add package ClickHouse.Driver ``` 也可以使用 NuGet ``` Install-Package ClickHouse.Driver ``` 要連接到 ClickHouse,官方文件推薦使用 ClickHouseConnection 下面是最基本的連線範例 #### 連到 ClickHouse ``` using ClickHouse.Driver.ADO; using var connection = new ClickHouseConnection( "Host=localhost;Port=8123;Database=default;Username=default;Password=mypassword;Protocol=http" ); await connection.OpenAsync(); ``` `Host`: ClickHouse Server location `Username`, `Password`: 登入帳密 我這邊透過 docker 已經運行起 ClickHouse 了 可以參考我的 `docker.compose.yaml` ``` version: '3.8' services: clickhouse: image: clickhouse/clickhouse-server:latest container_name: clickhouse-server environment: CLICKHOUSE_DB: clickhousedemo CLICKHOUSE_USER: default CLICKHOUSE_PASSWORD: mypassword ports: - "8123:8123" - "9000:9000" ulimits: nofile: soft: 262144 hard: 262144 volumes: - clickhouse_data:/var/lib/clickhouse volumes: clickhouse_data: ``` #### 建立 Table ``` var createTable = @" CREATE TABLE IF NOT EXISTS demo_logs ( timestamp DateTime, service String, latency_ms UInt32 ) ENGINE = Memory"; using (var cmd = connection.CreateCommand()) { cmd.CommandText = createTable; await cmd.ExecuteNonQueryAsync(); } ``` #### INSERT 一筆資料 ``` using (var insert = connection.CreateCommand()) { insert.CommandText = "INSERT INTO demo_logs (timestamp, service, latency_ms) VALUES (now(), 'demo_service_1', 123)"; await insert.ExecuteNonQueryAsync(); } ``` #### INSERT 多筆資料 ``` using (var insertMany = connection.CreateCommand()) { insertMany.CommandText = @" INSERT INTO demo_logs (timestamp, service, latency_ms) VALUES (now(), 'demo_service_2', 456), (now(), 'demo_service_3', 78), (now(), 'demo_service_4', 90)"; await insertMany.ExecuteNonQueryAsync(); } ``` #### 基礎的 SELECT 語法 ``` using (var select = connection.CreateCommand()) { select.CommandText = "SELECT timestamp, service, latency_ms FROM demo_logs"; using var reader = await select.ExecuteReaderAsync(); Console.WriteLine("Query Result : "); while (await reader.ReadAsync()) { var timestamp = reader.GetFieldValue<DateTime>(0); var service = reader.GetFieldValue<string>(1); var latency = reader.GetFieldValue<uint>(2); Console.WriteLine($"[{timestamp}] Service: {service}, Latency: {latency} ms"); } } ``` #### UPDATE and DELETE 資料 > ClickHouse 是針對 OLAP,所以不像 MySQL 那樣適合頻繁更新或刪除單筆資料 ``` // UPDATE using (var update = connection.CreateCommand()) { update.CommandText = @" ALTER TABLE demo_logs UPDATE latency_ms = 200 WHERE service = 'demo_service_1'"; await update.ExecuteNonQueryAsync(); } // DELETE using (var delete = connection.CreateCommand()) { delete.CommandText = @" ALTER TABLE demo_logs DELETE WHERE service = 'demo_service_4'"; await delete.ExecuteNonQueryAsync(); } ``` ### 結語 ClickHouse 的核心價值在於 **快速分析大資料**,在 OLAP 場景中非常強大,但設計上偏向 **讀取與分析**,不適合做頻繁單筆更新