编程 ClickHouse 深度实战:当实时 OLAP 撞上次世代列式引擎——MergeTree、稀疏索引与向量化执行如何重写数据分析心智模型

2026-07-15 04:11:41 +0800 CST views 10

ClickHouse 深度实战:当实时 OLAP 撞上次世代列式引擎——MergeTree、稀疏索引与向量化执行如何重写数据分析心智模型

关键词:ClickHouse、MergeTree、列式存储、向量化执行、稀疏索引、物化视图、跳过索引、ClickHouse Keeper、实时数仓、OLAP

写在前面:过去两年,DuckDB 让"进程内 OLAP"火了一把,但当你面对的是每秒几十万条写入、PB 级历史、亚秒级聚合的生产场景时,单机嵌入式引擎就不够看了。这篇文章从工程师视角,把 ClickHouse 这台"为分析而生的机器"彻底拆开:它不是又一个 SQL 数据库,而是一套关于"如何用 CPU 缓存、如何用磁盘、如何用内存"的极端工程哲学。读完你应该能回答三个问题——为什么它快、什么时候该用、怎么把它用对。


一、背景:为什么我们需要"为分析而生"的数据库

1.1 行存与列存:一次彻底的逆向思维

传统关系型数据库(MySQL、PostgreSQL)是为**事务(OLTP)设计的:一条订单、一行用户、一次更新。它们的存储是按行(row)**连续的——一条记录的 id、name、price、ctime 紧挨着躺在磁盘上。这对"取出完整一条记录"极其友好,但对"统计全天 GMV"是灾难:为了算一个 sum(price),数据库必须把整张表每一行的所有列都从磁盘读进内存,再丢掉 99% 用不上的字段。

ClickHouse 的底层假设完全反过来:分析查询几乎永远只碰少数列、但要扫极多行。于是它把同一列的数据连续存储

行存(MySQL): [id=1,name=A,price=10] [id=2,name=B,price=20] [id=3,name=C,price=30] ...
列存(ClickHouse):
  id  列: 1, 2, 3, 4, 5 ...
  name列: A, B, C, D, E ...
  price列:10,20,30,40,50 ...

列存带来两个直接红利:

  1. 只读取需要的列,I/O 量随查询列数线性下降;
  2. 同列数据类型一致,压缩率极高。价格列 10,20,30... 这种有序整数,配上 delta + LZ4/ZSTD,轻松达到 5:1 甚至 20:1 的压缩比。更少的字节 = 更少的磁盘 I/O = 更快的查询。

1.2 OLTP 与 OLAP:两种完全不同的负载

维度OLTP(MySQL)OLAP(ClickHouse)
典型操作单行 INSERT/UPDATE/DELETE批量 INSERT + 大范围聚合 SELECT
数据新鲜度强一致、实时近实时(秒级可查)
事务必须 ACID不需要跨行事务
索引B+ 树行级索引稀疏主键 + 跳过索引
写入模式随机小写入顺序大批量写入
Join频繁能避免就避免

ClickHouse 明确放弃了传统事务和行级 UPDATE/DELETE 的高频能力,把这些"负担"换成极致的扫描与聚合性能。这不是缺陷,而是取舍(trade-off)——它把"分析"这件事做到了工程极限。

1.3 ClickHouse 从哪来

ClickHouse 诞生于俄罗斯搜索巨头 Yandex 的内部项目 Yandex.Metrica(类似 Google Analytics),用来支撑每秒百万级的网站埋点写入与交互式多维分析。2016 年开源,2021 年成立独立公司 ClickHouse Inc.。今天的 v26.x(v26.2 为 2026 年初发布的稳定大版本)已经是一个成熟的、云原生友好的分析型 DBMS。

1.4 它解决的真实痛点

  • MySQL 跑聚合SELECT city, COUNT(*) FROM events GROUP BY city 在 10 亿行上,MySQL 可能要分钟级,ClickHouse 亚秒。
  • Elasticsearch 太贵:ES 的倒排索引为全文检索而生,做数值聚合时内存与存储成本陡增;ClickHouse 用列存 + 跳过索引把成本压到 ES 的零头。
  • Hadoop/Spark 太重:离线批处理 T+1,等不起;ClickHouse 是实时的,写入即可查。

二、核心概念:ClickHouse 到底是怎么"快"的

2.1 向量化执行(Vectorized Execution):别一次算一个数

传统数据库执行 price * 1.1逐行的:取一个数 → 乘 → 存,再取下个。现代 CPU 的厉害之处在于 SIMD(单指令多数据) 和深层流水线——一次对一批数据做同样的运算才能喂饱它。

ClickHouse 的执行引擎是列式、批处理的:它把数据按 block(默认 65536 行)成批流经算子,每个函数调用一次处理一整列。这带来:

  • 函数调用开销被摊薄(每 65536 行才调用一次);
  • 内存访问是顺序的,CPU cache 命中率高;
  • 可充分利用现代编译器的自动向量化(配合 -msse4.2/avx 指令集)。

心智模型:ClickHouse 的查询像一条流水线工厂,数据是一箱一箱的零件(block),每个工人(函数)对整箱做同一种操作,而不是把单个零件传来传去。

2.2 MergeTree:一切的起点

MergeTree 是 ClickHouse 的核心表引擎家族。它的设计极其优雅:写入即追加,合并在后台

  • 数据写入时,先落到内存,再 flush 成一个个 part(数据 part,旧称"分区块") 文件;
  • 后台线程异步把这些小 part merge(合并) 成大 part,过程类似 LSM-Tree 的 compaction;
  • 查询时,ClickHouse 会并行扫描相关 part,并跳过明显不匹配的 part。
CREATE TABLE events
(
    event_date Date,
    event_time DateTime,
    user_id    UInt64,
    event_type LowCardinality(String),
    url        String,
    duration   UInt32,
    price      Nullable(Float64)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_date)        -- 按月分区
ORDER BY (event_type, user_id, event_time)  -- 排序键 = 主键
TTL event_date + INTERVAL 180 DAY;       -- 180 天后自动清理冷数据

几个关键点:

  • ORDER BY 就是主键(primary key 默认等于 ORDER BY,除非显式指定 PRIMARY KEY)。它决定数据在 part 内的物理排序,是稀疏索引的基础。
  • PARTITION BY 决定 part 的粗粒度切分。查询带上分区条件能直接跳过整批 part(partition pruning)。
  • TTL 让冷数据自动过期,避免手动删表的运维噩梦。

2.3 稀疏主键索引(Sparse Primary Index):用极小代价定位数据

和 MySQL 的 B+ 树(每行一条索引项)不同,ClickHouse 的主键是稀疏的:

  • 数据按 index_granularity(默认 8192 行)切成一个个 granule(颗粒)
  • 主键索引只为每个 granule 的第一行记录一条标记(mark);
  • 查询时,先在主键上做"粗糙"二分,定位到可能包含目标值的 granule 区间,再读这些 granule 做精确过滤。
主键 (event_type, user_id, event_time) 的 mark 文件(每个 granule 一条):
  ('click', 1001, 2026-07-01 08:00)  -> mark #0
  ('click', 1001, 2026-07-01 10:00)  -> mark #1
  ...

这意味着主键索引本身极小(数据量的 1/8192 量级),即使百亿行,索引也能轻松放进内存。这是 ClickHouse 能"扫全表也很便宜"的根本原因之一。

⚠️ 常见误区:ClickHouse 的主键不等于唯一约束。它不保证唯一,只是排序与索引依据。去重要靠 ReplacingMergeTreeFINAL/GROUP BY

2.4 MergeTree 家族:用引擎表达业务语义

ClickHouse 把很多"业务聚合逻辑"直接下沉到引擎层:

引擎解决什么核心机制
MergeTree最通用后台 merge
ReplacingMergeTree去重最新版本(CDC 场景)ver 列保留最新
SummingMergeTree自动预求和相同排序键的行在 merge 时相加
AggregatingMergeTree预聚合(配合物化视图)存聚合函数中间态(-State/-Merge)
CollapsingMergeTree行级"抵消"(±1 标记)成对折叠
VersionedCollapsingMergeTree带版本的折叠支持乱序到达

例如,统计每个用户的累计消费,用 SummingMergeTree 可以让后台 merge 自动把同一 (user_id) 的多条记录求和,查询时直接读"已半聚合"的数据:

CREATE TABLE user_revenue
(
    user_id UInt64,
    date Date,
    revenue AggregateFunction(sum, Float64)   -- 注意这里存的是"聚合态"
)
ENGINE = AggregatingMergeTree
ORDER BY (user_id, date);

2.5 压缩:被严重低估的性能开关

ClickHouse 每列可单独指定 codec。默认是 LZ4,但选对 codec 收益巨大:

CREATE TABLE metrics
(
    ts        DateTime CODEC(Delta, ZSTD),  -- 时间戳:先 delta 再 ZSTD,压缩比惊人
    host      LowCardinality(String),        -- 低基数字段:字典编码
    value     Float64 CODEC(Gorilla, ZSTD), -- 浮点监控值:Gorilla 专治时序
    labels    String
)
ENGINE = MergeTree
ORDER BY (host, ts);
  • Delta:对递增的时间戳/ID,差值很小,压缩率飙升;
  • Gorilla:Facebook 为时序浮点设计的 codec,相邻值差异编码;
  • LowCardinality:把 host 这种取值有限的列做字典化,既省空间又加速分组。

三、架构分析:从单机到集群

3.1 节点角色与分布式表

ClickHouse 的集群是无中心(shared-nothing)的:每个节点都是对等的计算+存储单元,没有主节点。横向扩展靠分片(shard),高可用靠副本(replica)

分布式的核心是两个引擎:

-- 1) 本地表:真正存数据的表(每个分片一份)
CREATE TABLE events_local ON CLUSTER '{cluster}' AS events
ENGINE = ReplicatedMergeTree('/clickhouse/{shard}/tables/events', '{replica}')
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_type, user_id, event_time);

-- 2) 分布式表:一张"虚拟"的路由表,把查询分发到各分片
CREATE TABLE events_dist AS events_local
ENGINE = Distributed('{cluster}', default, events_local, cityHash64(user_id));

Distributed 引擎本身不存数据,它根据分片键 cityHash64(user_id) 把写入路由到对应分片,查询时并行下发到所有分片再汇总。对应用层来说,读写 events_dist 就像操作一张表

3.2 ClickHouse Keeper:ZooKeeper 的 Raft 替代

老版本 ClickHouse 依赖 ZooKeeper 做副本协调,但 ZK 是 JVM 写的、运维复杂、有单点瓶颈。新版本用 ClickHouse Keeper——一个用 C++ 实现、基于 Raft 共识协议、消息格式兼容 ZK 的组件,直接嵌在 ClickHouse 生态里,延迟更低、吞吐更高、运维更省心。

<!-- keeper 配置片段 -->
<keeper_server>
    <tcp_port>9181</tcp_port>
    <server_id>1</server_id>
    <raft_configuration>
        <server><id>1</id><hostname>ck-keeper-1</hostname><port>9234</port></server>
        <server><id>2</id><hostname>ck-keeper-2</hostname><port>9234</port></server>
        <server><id>3</id><hostname>ck-keeper-3</hostname><port>9234</port></server>
    </raft_configuration>
</keeper_server>

3.3 写入路径:别小看"怎么写"

ClickHouse 对大批量顺序写极其友好,但对单行随机写很糟糕。生产实践:

  • async_insert:客户端把小写入先攒成一批,服务端异步落盘,大幅降低 part 数量爆炸;
  • max_insert_block_size:控制单次插入的 block 大小;
  • Buffer 表引擎:在内存中缓冲写入,周期性 flush,削峰填谷;
  • Kafka 表引擎:直接 SELECT 消费 Kafka topic 写入,做实时管道。
-- 用 Kafka 引擎把埋点实时灌进 ClickHouse
CREATE TABLE kafka_events
(
    user_id UInt64,
    event_type String,
    payload   String
)
ENGINE = Kafka
SETTINGS kafka_broker_list = 'kafka:9092',
         kafka_topic_list = 'raw_events',
         kafka_group_name = 'ch_consumer',
         kafka_format = 'JSONEachRow';

-- 物化视图把 Kafka 流实时落到 MergeTree
CREATE MATERIALIZED VIEW mv_events TO events_local AS
SELECT user_id, event_type, now() AS event_time, ...
FROM kafka_events;

3.4 ClickStack:把可观测性也吞进来

2025–2026 年 ClickHouse 力推 ClickStack——把 ClickHouse 与可视化(类 Grafana)打包成开源可观测性栈,用一份列存同时搞定 logs、metrics、traces。它背后是 ClickHouse 用同一套引擎承载"可观测性三件套"的能力:高写入吞吐、廉价存储、灵活聚合。对工程师的意义是:你的业务数仓和监控栈可以用同一个数据库,少一套技术栈、少一份运维。


四、代码实战:从零跑起来

4.1 用 Docker Compose 起一个单节点

# docker-compose.yml
version: '3.8'
services:
  clickhouse:
    image: clickhouse/clickhouse-server:25.8
    environment:
      CLICKHOUSE_DB: default
      CLICKHOUSE_USER: default
      CLICKHOUSE_PASSWORD: ""
      CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
    ports:
      - "8123:8123"   # HTTP 接口
      - "9000:9000"   # 原生 TCP 接口
    volumes:
      - ./ch_data:/var/lib/clickhouse
      - ./config.xml:/etc/clickhouse-server/config.d/override.xml:ro
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
docker compose up -d
# 连进去
docker exec -it $(docker ps -qf ancestor=clickhouse/clickhouse-server) clickhouse-client

4.2 建表与设计排序键(这是性能的地基)

设计原则:把"最常被等值/范围过滤、且区分度高"的列放 ORDER BY 前面,让稀疏索引最大化和分区裁剪配合。

CREATE TABLE default.web_events
(
    event_date    Date          DEFAULT toDate(event_time),
    event_time    DateTime64(3) CODEC(Delta, ZSTD),
    user_id       UInt64,
    session_id    UUID,
    event_type    LowCardinality(String),
    country       LowCardinality(FixedString(2)),
    device        LowCardinality(String),
    url           String,
    duration_ms   Nullable(UInt32),
    props         JSON,                       -- 半结构化字段,无需预先定义 schema
    revenue       Nullable(Float64) CODEC(ZSTD)
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(event_date)          -- 按天分区,便于 TTL 与裁剪
ORDER BY (event_type, country, event_time, user_id)
TTL event_date + INTERVAL 90 DAY
SETTINGS index_granularity = 8192;

注意 JSON 类型(2024 后趋于成熟的原生 JSON 列):它把 JSON 内部字段自动展开为子列做列存,兼顾灵活性与性能,不用再手动 String 然后 JSONExtract 全表解析。

4.3 写入:三种典型姿势

-- 姿势 1:直接 INSERT VALUES(小批量)
INSERT INTO web_events
    (event_time, user_id, event_type, country, url, duration_ms, props)
VALUES
    (now(), 10001, 'page_view', 'CN', '/home', 1200, '{"utm":"x","ab":"B"}'),
    (now(), 10002, 'click',     'US', '/buy',  300,  '{"utm":"y"}');

-- 姿势 2:从文件批量导入(超快)
INSERT INTO web_events
    SELECT * FROM file('events.jsonl', 'JSONEachRow');

-- 姿势 3:HTTP 接口灌入(数据管道常用)
curl 'http://localhost:8123/?query=INSERT%20INTO%20web_events%20FORMAT%20JSONEachRow' \
     -d '{"event_time":"2026-07-15 10:00:00","user_id":1,"event_type":"click","country":"CN","url":"/p"}'

4.4 物化视图:把聚合提前算好

这是 ClickHouse 最有"爽点"的特性之一。与其每次查询都 GROUP BY 十亿行,不如在数据写入时增量预聚合

-- 明细表
CREATE TABLE events_raw
(
    event_time DateTime,
    user_id    UInt64,
    event_type LowCardinality(String),
    revenue   Float64
)
ENGINE = MergeTree
ORDER BY (event_type, event_time);

-- 预聚合表:每分钟每类型的总额与次数
CREATE TABLE events_by_min
(
    event_date Date,
    minute     DateTime,
    event_type LowCardinality(String),
    cnt        AggregateFunction(count),
    sum_rev    AggregateFunction(sum, Float64)
)
ENGINE = AggregatingMergeTree
ORDER BY (event_type, minute);

-- 物化视图:写入 raw 时自动流入聚合表
CREATE MATERIALIZED VIEW mv_events_by_min
TO events_by_min
AS SELECT
    toDate(event_time)          AS event_date,
    toStartOfMinute(event_time) AS minute,
    event_type,
    countState()                AS cnt,
    sumState(revenue)           AS sum_rev
FROM events_raw
GROUP BY event_date, minute, event_type;

-- 查询时"合并"聚合态
SELECT
    event_type,
    sumMerge(sum_rev) AS total_revenue,
    countMerge(cnt)   AS total_cnt
FROM events_by_min
WHERE minute >= now() - INTERVAL 1 HOUR
GROUP BY event_type;

物化视图是写入时触发、对应用透明的。应用只管写 events_raw,查询读 events_by_min 即可获得数量级提速。

4.5 跳过索引(Skipping Index):让查询"跳过"无关数据

稀疏主键只能沿 ORDER BY 列加速。对 url 这种不在排序键里的字符串做过滤,就要靠跳过索引

ALTER TABLE web_events
ADD INDEX idx_url url TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4;

-- 全文检索:inverted index(2024 后走向成熟)
ALTER TABLE web_events
ADD INDEX idx_url_inv url TYPE inverted GRANULARITY 1;

-- 查某 URL 的访问
SELECT count() FROM web_events WHERE url LIKE '%checkout%';
-- ClickHouse 先读跳过索引,直接排除不含该 token 的 granule
  • tokenbf_v1:基于 Bloom Filter,适合"是否包含某 token"的成员判断;
  • inverted:真正倒排索引,支持 hasToken()、分词全文检索,替代 ES 的轻量方案;
  • set(1000)minmaxngrambf_v1 等各有所长。

4.6 Projection:同一份数据的"另一种排序"

Projection 是表上的辅助物理视图——它按不同排序键额外存一份数据,查询优化器自动选择用主表还是投影。

ALTER TABLE web_events
ADD PROJECTION proj_by_user
(
    SELECT * ORDER BY (user_id, event_time)
);

-- 触发后台物化(已有数据需要物化)
ALTER TABLE web_events MATERIALIZE PROJECTION proj_by_user;

-- 按 user_id 过滤的查询会自动走 proj_by_user,快得飞起
SELECT * FROM web_events WHERE user_id = 10001;

Projection 与物化视图的区别:投影共享同一张表的写入语义、自动维护、对查询透明,适合"同一份数据需要多种访问顺序"的场景。

4.7 用 Python / Go 读写(真实应用代码)

# pip install clickhouse-driver
from clickhouse_driver import Client

client = Client(host='localhost', port=9000, database='default')

# 批量写入(注意用 executemany / INSERT 的列格式)
rows = [
    (10001, 'click', 19.9),
    (10002, 'view',  None),
]
client.execute(
    "INSERT INTO events_raw (user_id, event_type, revenue) VALUES",
    rows
)

# 查询
result = client.execute(
    "SELECT event_type, count() FROM events_raw GROUP BY event_type"
)
for event_type, cnt in result:
    print(event_type, cnt)
// go get github.com/ClickHouse/clickhouse-go/v2
package main

import (
    "context"
    "time"
    "github.com/ClickHouse/clickhouse-go/v2"
    "github.com/ClickHouse/clickhouse-go/v2/lib/driver"
)

func main() {
    conn, err := clickhouse.Open(&clickhouse.Options{
        Addr: []string{"localhost:9000"},
        Auth: clickhouse.Auth{Database: "default"},
    })
    if err != nil { panic(err) }
    ctx := context.Background()

    batch, _ := conn.PrepareBatch(ctx, "INSERT INTO events_raw")
    for i := 0; i < 1000; i++ {
        _ = batch.Append(
            time.Now(),
            uint64(i),
            "click",
            9.9,
        )
    }
    _ = batch.Send()
}

4.8 查询示例:分析语句长什么样

-- 各国 TOP 页面的 PV 与平均停留,最近 7 天
SELECT
    country,
    url,
    count()                         AS pv,
    avg(duration_ms)               AS avg_dur,
    quantile(0.95)(duration_ms)    AS p95_dur
FROM web_events
WHERE event_date >= today() - 7
  AND event_type = 'page_view'
GROUP BY country, url
ORDER BY pv DESC
LIMIT 20;

-- 漏斗:从浏览到下单的转化率
SELECT
    countIf(event_type = 'page_view')                                    AS views,
    countIf(event_type = 'add_cart')                                     AS carts,
    countIf(event_type = 'purchase')                                     AS purchases,
    round(purchases / views, 4)                                          AS cvr
FROM web_events
WHERE event_date = today();

五、性能优化:把 ClickHouse 用对

5.1 ORDER BY / PRIMARY KEY 是第一杠杆

经验法则:

  1. 等值过滤高频的列放最前(最大程度 prune);
  2. 范围过滤(时间)通常紧随其后;
  3. 避免把超高基数列(如 user_id)放太前——它会让 granule 内数据过于分散,稀疏索引退化;
  4. 排序键不宜过长(影响 merge 成本与索引体积)。

5.2 分区粒度:别太细也别太粗

  • 天/月分区是常态;
  • 太细(按小时)→ part 数量爆炸,merge 压力大;
  • 太粗(按年)→ 分区裁剪失效,单 part 过大。

5.3 跳过索引的选型决策树

  • 等值/集合成员判断 → set / bloom_filter
  • 字符串包含/token → tokenbf_v1 → 真全文 → inverted
  • 数值/日期范围过滤且不在主键 → minmax / ngrambf_v1
  • 前缀匹配 → ngrambf_v1

5.4 物化视图 vs Projection vs 普通表

手段透明性维护成本适用
物化视图需改写查询目标表中(独立表)跨表聚合、异构目标
Projection完全透明低(随主表)同表多排序维度
预聚合表(手动)需改写极致可控

原则:能靠排序键/投影解决的,别上物化视图;需要跨维度重算的,再上物化视图。

5.5 关键 session/query settings

-- 调优时常用的几个开关(可在查询前 SET,或写进 users.xml)
SET max_threads = 16;              -- 并行度,通常 = 核数
SET max_memory_usage = 10000000000;-- 单查询内存上限(~10GB)
SET use_uncompressed_cache = 1;    -- 热数据解压缓存
SET max_insert_block_size = 1048576;
SET async_insert = 1;              -- 客户端异步攒批
SET optimize_read_in_order = 1;    -- 利用排序键做有序读,省排序
SET query_profiler_real_time_period_ns = 1000000;  -- 开启剖析

5.6 查询剖析:别靠猜,看 Pipeline

-- 看执行计划(是否读了正确的索引/投影)
EXPLAIN PLAN actions=1, indexes=1
SELECT * FROM web_events WHERE user_id = 10001;

-- 看算子流水线(vectorized pipeline)
EXPLAIN PIPELINE SELECT count() FROM web_events;

-- 查慢查询日志
SELECT
    query_duration_ms,
    read_rows,
    memory_usage,
    query
FROM system.query_log
WHERE type = 'QueryFinish'
  AND query_duration_ms > 1000
ORDER BY query_duration_ms DESC
LIMIT 10;

-- 实时看正在跑的查询
SELECT * FROM system.processes ORDER BY elapsed DESC;

system.query_logsystem.mergessystem.partssystem.query_thread_log 是排查问题的四件套。

5.7 压缩 codec 的 ROI

监控 system.columns 里每列的压缩比,对高基数列盲目上 ZSTD(1) 可能反而拖慢写入;时序类用 Delta, ZSTD 几乎稳赚。LowCardinality 对状态/国家/设备这种列是免费午餐。

5.8 常见反模式(踩坑清单)

  1. 单行高频 INSERT → part 爆炸,merge 跟不上;老老实实批量 + async_insert
  2. 滥用 FINALReplacingMergeTreeFINAL 会实时合并,巨慢;优先用 GROUP BY 取最新。
  3. ORDER BY 放 UUID/随机列 → 数据完全无序,稀疏索引失效。
  4. 在 ClickHouse 里做复杂多表大 Join → 它能 join,但不擅长;大表 join 尽量用 PREWHERE、先过滤、或把维度表走 Dictionary
  5. 把 ClickHouse 当 OLTP 用 → 频繁 UPDATE/DELETE 是它弱项,别用它做业务主库。

六、总结与展望

6.1 ClickHouse 适合什么

  • 日志 / 埋点 / 时序的实时分析与监控
  • 用户行为 / 广告 / 风控的多维即席查询
  • 需要 PB 级扫描、亚秒聚合、廉价存储的数据仓库
  • 替代 ES 做"带全文检索的分析"。

6.2 它与兄弟们的边界

  • DuckDB:进程内、单机、嵌入应用的轻量 OLAP。适合分析师本地跑 Parquet。ClickHouse 是服务端、分布式、高并发的。两者互补,不少架构让 DuckDB 做边缘、ClickHouse 做中心。
  • StarRocks / Doris:也做实时 OLAP,StarRocks 在"高并发点查 + 多表 Join"上更激进。ClickHouse 胜在写入吞吐、生态成熟度、单表聚合极限性能。选型看你是"写多查杂"还是"查多 JOIN 多"。
  • Elasticsearch:全文检索王者,但聚合贵。ClickHouse + inverted 索引能蚕食其分析场景。
  • 云数仓(BigQuery/Snowflake):托管省心但按量计费贵、延迟高。ClickHouse 自建成本更低、延迟更低,代价是运维。

6.3 趋势:实时 OLAP 正在"吞噬"一切

  • ClickStack 把 logs/metrics/traces 统一进一个引擎;
  • Lakehouse 集成:ClickHouse 可直接查 S3 上的 Parquet/Delta,成为"开放表格式"上的查询加速层;
  • 原生 JSON 与动态子列让半结构化数据不再需要 ETL 预定义;
  • Keeper 成熟 + 云原生部署让运维门槛持续下降;
  • 向量检索(experimental)让它开始触碰 AI Embedding 场景。

6.4 给工程师的三条建议

  1. 先用对引擎,再谈调优:选错 ORDER BY、滥写单行,再怎么调参也救不回来。
  2. 把聚合下沉到写入侧:物化视图 / 预聚合表是 ClickHouse 的"免费午餐",能提前算的别留到查询时。
  3. 用系统表做循证优化system.query_log 比任何"经验"都诚实。性能问题先看数据,再下结论。

结语

ClickHouse 不是"又一个数据库",它是一种关于数据如何落盘、如何被 CPU 吃掉、如何被网络搬运的极端工程态度。它用列存换 I/O、用向量化换吞吐、用稀疏索引换内存、用后台 merge 换写入自由。当你真正理解它的取舍,你会发现自己不再"调数据库",而是在设计数据在磁盘与内存之间的旅行路线

如果说 DuckDB 让"在自己的笔记本上分析一个 G 的文件"变得理所当然,那么 ClickHouse 让"用一堆廉价机器撑起全公司的实时分析"变得理所当然。这,就是 2026 年工程师手里最锋利的那把 OLAP 刀。


参考资料:ClickHouse 官方文档 Changelog 2026、ClickHouse v26.2 发布说明、ClickStack 官方博客、《大数据核心框架更新周报》(2026-07)、ClickHouse GitHub 仓库。本文代码均基于 ClickHouse 25.x/26.x 语法,低版本部分特性(如原生 JSON、inverted 索引)需开启对应 experimental 设置。

推荐文章

js常用通用函数
2024-11-17 05:57:52 +0800 CST
批量导入scv数据库
2024-11-17 05:07:51 +0800 CST
关于 `nohup` 和 `&` 的使用说明
2024-11-19 08:49:44 +0800 CST
程序员茄子在线接单