ClickHouse 学习文档

第1章 技术评估与选型分析

1.1 ClickHouse 的本质定位

ClickHouse 是面向实时分析与 OLAP 的高性能列式数据库。它最核心的设计目标不是“替代所有数据库”,
而是用列式存储、数据排序、稀疏主键索引、向量化执行、多线程并行和高效压缩,快速完成大规模数据扫描与聚合。

历史上 ClickHouse 起源于 Yandex 的内部项目并开源;今天应理解为一个独立发展的开源数据库项目和商业生态,
而不是简单表述为“Yandex 的数据库”。

OLTP = 做业务(增删改查)
OLAP = 做分析(统计、聚合、报表)

核心特征

  • 列式存储:只读取查询真正需要的列。
  • MergeTree 存储家族:通过 Part、后台 Merge 和排序键实现高吞吐写入与高效读取。
  • 稀疏主键索引:以 granule 为基本粒度进行数据跳过。
  • 向量化执行:批量处理数据,降低逐行解释执行开销。
  • 多线程并行:单机查询也可以利用多个 CPU 核心。
  • 强压缩能力:数据类型、排序顺序和编码共同决定压缩效果。
  • 实时摄入:支持批量 INSERT、异步 INSERT、Kafka、S3 等多种入口。
  • 现代 SQL 能力:JOIN、窗口函数、CTE、JSON、全文检索、UPDATE/DELETE 等能力持续增强。
  • 分布式能力:可通过分片、副本、Distributed 表和 Keeper 构建集群。
  • 云与对象存储能力:现代 ClickHouse 架构不应简单等同于传统“纯共享无”架构,具体部署形态取决于 OSS、云服务和存储方案。

1.2 与其他数据库的选型对比

维度 ClickHouse PostgreSQL/MySQL Elasticsearch Apache Doris
核心定位 OLAP/实时分析 OLTP/通用关系型 搜索/日志/分析 OLAP/数仓
存储 列式 行式 倒排 + 列式结构 列式
强项 大规模扫描、聚合、实时分析 事务、点查、业务写入 全文检索、搜索体验 数仓、BI、SQL 分析
UPDATE/DELETE 支持,但设计目标仍偏分析;新版本有轻量更新/删除 原生强项 支持文档更新 支持
JOIN 能力持续增强,支持多种 JOIN 非传统关系型 JOIN
事务 不应作为 OLTP 事务数据库使用 ACID 强 非传统事务模型 以分析场景为主
高并发点查 非首选 视场景
大规模聚合 数据量大后成本高 可用但搜索模型更强
全文搜索 现在具备原生 Text 索引等能力 依赖扩展/设计 强项 视版本和方案
运维 中等 较低 中高 中等
典型用途 日志、指标、行为、实时数仓 订单、用户、支付、库存 搜索、日志检索 BI、数仓、实时分析

不要使用“千万行以下 ClickHouse 没优势、亿级以上一定最佳”这种硬阈值。
数据量不是唯一变量。数据宽度、查询模式、选择性、并发、存储、数据更新方式以及团队运维能力都会影响选型。

1.3 为什么 ClickHouse 快

1.3.1 列式存储

假设表有 30 列,而查询只需要 3 列:

SELECT user_id, event_type, event_time
FROM events
WHERE event_type = 'click';

列式存储可以避免读取大量无关列,从而显著降低 I/O 和解压成本。

1.3.2 Granule 与稀疏主键索引

ClickHouse 的 MergeTree 数据被组织成 Part,Part 内按排序键排列,并进一步划分为 granule。

默认 index_granularity 常见值是 8192,但应注意:

8192 是索引粒度/默认 granule 相关设置,不应简单描述成“向量化执行每批固定 8192 行”。

主键索引不是传统意义上的“每行一个 B+Tree 索引”,而是帮助 ClickHouse 跳过不相关 granule。

1.3.3 数据排序

ORDER BY 决定数据的物理排序方式,是 ClickHouse 表设计最重要的决策之一。

ORDER BY (tenant_id, event_type, event_time)

如果大量查询围绕这些维度进行过滤、范围扫描或聚合,数据局部性会明显改善。

1.3.4 向量化与并行执行

ClickHouse 会对数据进行批量处理,并充分利用 CPU 并行能力。
因此其优势来自多个层面的组合,而不是单一“SIMD 技术”。

1.3.5 编码与压缩

常见组合包括:

  • LowCardinality
  • Delta
  • DoubleDelta
  • Gorilla
  • T64
  • LZ4
  • ZSTD

压缩比没有固定的“10 倍、20 倍”保证,实际结果取决于数据分布、排序键和字段类型。

1.3.6 JIT (即时编译)

ClickHouse 具备表达式 JIT 编译能力,但不要把 JIT 当作所有查询都自动执行的核心机制。
性能分析时仍应首先关注:

  1. 是否读取过多数据;
  2. 主键是否有效;
  3. 是否存在过多 Part;
  4. JOIN/聚合是否产生内存瓶颈;
  5. 是否需要预聚合或 Projection;
  6. 是否存在不必要的函数计算。

1.4 选型决策树

业务主要需求是什么?
│
├─ 强事务:支付、库存、账户余额、订单状态机
│   └─ OLTP 数据库优先,ClickHouse 作为分析库
│
├─ 全文搜索是第一诉求
│   ├─ 强搜索体验、复杂文本检索 → Elasticsearch/OpenSearch 等可优先考虑
│   └─ 搜索 + 大规模分析 → ClickHouse 也可纳入方案
│
├─ 大规模分析、聚合、报表、日志、指标、行为数据
│   └─ ClickHouse 是强候选
│
├─ 需要频繁 UPDATE/DELETE
│   ├─ 高频事务级变更 → OLTP 更合适
│   └─ 分析型修正、CDC、慢变维 → ClickHouse 可采用 ReplacingMergeTree、
│      轻量 UPDATE/DELETE、patch parts 等方案
│
├─ JOIN 很多
│   └─ 不再简单地认为 ClickHouse “不支持 JOIN”
│      应通过数据规模、JOIN 类型、算法、排序和过滤情况评估
│
└─ 延迟要求
    ├─ 秒级/亚秒级分析 → ClickHouse 常见优势
    └─ 极低延迟点查 → 通常选择更适合点查的存储

第2章 核心概念深度解析

2.1 MergeTree 引擎家族

2.1.1 MergeTree

基础 MergeTree 适用于追加型明细数据。

CREATE TABLE events
(
    event_date Date,
    event_time DateTime64(3),
    event_type LowCardinality(String),
    user_id UInt64,
    page_url String,
    duration_ms UInt32
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_type, user_id, event_time);

核心概念:

  • INSERT 会形成新的 Part;
  • Part 在后台不断 Merge;
  • Part 内的数据按排序键排序;
  • Merge 不是“可选优化”,而是 ClickHouse 存储生命周期的重要组成部分;
  • 频繁小 INSERT 会制造大量小 Part。

2.1.2 ReplacingMergeTree

适用于“最终状态/去重”类模型。

CREATE TABLE user_latest
(
    user_id UInt64,
    name String,
    email String,
    updated_at DateTime64(3),
    is_deleted UInt8
)
ENGINE = ReplacingMergeTree(updated_at)
ORDER BY user_id;

关键认知:

  • 去重发生在后台 Merge 过程中;
  • 查询时不保证数据已经完成去重;
  • FINAL 可以在查询阶段强制进行相应的合并语义,但可能增加资源消耗;
  • 去重范围与排序键、分区设计有关;
  • 版本列应能够稳定表示新旧版本;
  • 多副本场景要特别注意写入顺序和版本设计。

因此:

SELECT *
FROM user_latest FINAL
WHERE user_id = 1;

适合用于需要严格读取最终状态的场景,但不应无脑对所有大查询使用 FINAL

2.1.3 SummingMergeTree

适合可加和指标。

CREATE TABLE daily_sales
(
    date Date,
    product_id UInt32,
    shop_id UInt32,
    sales UInt64,
    amount Decimal(18, 2)
)
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(date)
ORDER BY (date, product_id, shop_id);

注意:

  • 后台 Merge 时会对相同排序键的数据进行聚合;
  • 查询时通常仍推荐显式 GROUP BY + sum(),因为不同 Part 之间可能尚未完成合并;
  • 非求和字段的取值规则不能被当作“业务上确定的唯一值”。

2.1.4 AggregatingMergeTree

用于保存聚合状态。

CREATE TABLE page_stats
(
    date Date,
    page_id UInt32,
    pv SimpleAggregateFunction(sum, UInt64),
    uv AggregateFunction(uniq, UInt64),
    avg_duration AggregateFunction(avg, Float64)
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(date)
ORDER BY (date, page_id);

物化视图:

CREATE MATERIALIZED VIEW page_stats_mv
TO page_stats
AS
SELECT
    toDate(event_time) AS date,
    page_id,
    count() AS pv,
    uniqState(user_id) AS uv,
    avgState(duration) AS avg_duration
FROM raw_events
GROUP BY date, page_id;

查询:

SELECT
    date,
    page_id,
    sum(pv) AS pv,
    uniqMerge(uv) AS uv,
    avgMerge(avg_duration) AS avg_duration
FROM page_stats
GROUP BY date, page_id;

2.1.5 CollapsingMergeTree

使用 Sign 表达新增和取消语义。

它不是普通意义上的“UPDATE 引擎”,而是一种特殊的数据建模方式。
生产环境必须严格理解写入顺序、重复事件和查询语义,否则很容易得到错误结果。

新项目如果只是需要“最新状态”,通常优先评估:

  • ReplacingMergeTree;
  • CDC + ReplacingMergeTree;
  • 现代轻量 UPDATE/DELETE;
  • 或直接使用更适合的 OLTP 数据库。

2.1.6 VersionedCollapsingMergeTree

在 CollapsingMergeTree 的基础上增加版本信息,用于更复杂的乱序/并发场景。
它属于高级模型,不建议在没有明确业务模型和测试验证时直接采用。

2.1.7 引擎选择建议

需求 首选
追加明细 MergeTree
最新状态/CDC 去重 ReplacingMergeTree
可加和指标 SummingMergeTree
聚合状态、UV、分位数等 AggregatingMergeTree
特殊折叠模型 Collapsing/VersionedCollapsingMergeTree
高频 OLTP 更新 优先 OLTP;分析侧可用 CDC 同步

2.2 Part、Partition 与 Merge

Partition 是“数据分区”,Part 是“分区里的实际数据文件”,Merge 是“后台把多个 Part 合并成更大的 Part”。

2.2.1 Part (分片)

每次 INSERT 通常会产生一个或多个数据 Part。
Part 是 ClickHouse 物理存储与 Merge 的核心单位。

查看:

SELECT
    database,
    table,
    partition,
    name,
    rows,
    bytes_on_disk,
    active,
    modification_time
FROM system.parts
WHERE active = 1
ORDER BY modification_time DESC
LIMIT 100;

2.2.2 Partition (分区)

Partition 的主要价值是:

  • 逻辑上的数据划分
  • 生命周期管理;
  • 分区裁剪;
  • 快速 DROP/DETACH/ATTACH;
  • 备份和归档边界。

不要把 Partition 当成主要查询索引。

常见设计:

PARTITION BY toYYYYMM(event_time)

但按天分区也不一定“错误”。是否合适取决于:

  • 每天数据量;
  • 写入频率;
  • 生命周期;
  • 分区数量;
  • 查询模式;
  • 运维需求。

2.2.3 不要死记“1 万分区就是危险”

分区数量没有统一的绝对阈值。
真正需要重点监控的是:

  • 活跃 Part 数;
  • 每个分区的 Part 数;
  • Merge backlog;
  • 磁盘 I/O;
  • 查询读取量;
  • Keeper/复制状态。

2.2.4 分区操作

ALTER TABLE events DROP PARTITION '202401';

ALTER TABLE events DETACH PARTITION '202401';

ALTER TABLE events ATTACH PARTITION '202401';

ALTER TABLE events REPLACE PARTITION '202401'
FROM events_staging;

2.3 ORDER BY、PRIMARY KEY 与主键索引

2.3.1 两者区别

ORDER BY (tenant_id, event_type, event_time)
PRIMARY KEY (tenant_id, event_type)

ORDER BY 决定物理排序。

PRIMARY KEY 决定主键索引使用的列,必须是 ORDER BY 的前缀。

两者都不表示唯一约束

2.3.2 排序键设计:不要机械套“高基数优先”

原文中的“高基数列在前、低基数列在后”过于绝对,且在 ClickHouse 中容易误导。

实际应该:

  1. 从真实 WHERE/范围查询模式出发;
  2. 考虑等值过滤、范围过滤和排序;
  3. 考虑列之间的基数与数据局部性;
  4. 通常让有选择性的、稳定的过滤维度成为排序前缀;
  5. 对低基数列放前也可能非常有效,尤其在数据局部性和压缩方面;
  6. 最终通过 EXPLAIN indexes = 1 和真实数据验证。

例如:

-- 查询经常是:
WHERE tenant_id = ?
  AND event_type = ?
  AND event_time BETWEEN ? AND ?

ORDER BY (tenant_id, event_type, event_time);

通常比简单把 event_time 放第一列更合理。

2.3.3 时间列是否一定放最后?

不是。

例如大量查询是:

WHERE event_time >= now() - INTERVAL 1 DAY

且没有 tenant/event_type 过滤,那么:

ORDER BY event_time

可能比:

ORDER BY event_type, user_id, event_time

更合适。

排序键必须由查询模式决定,而不是由固定口诀决定。


2.4 Skip Index

Skip Index 是二级数据跳过索引,应该在主键/排序键和 Projection 优化之后再考虑。

常见类型:

  • minmax
  • set
  • bloom_filter
  • tokenbf_v1
  • ngrambf_v1
  • 新版本还提供原生 text 倒排索引能力

示例:

ALTER TABLE events
ADD INDEX idx_amount amount TYPE minmax GRANULARITY 4;

Bloom Filter:

ALTER TABLE events
ADD INDEX idx_user_tag user_tag TYPE bloom_filter GRANULARITY 4;

重要原则:

Skip Index 只有在数据与排序存在一定相关性、查询选择性足够高时才可能显著减少扫描。

不要为了“有索引就快”而大量创建。


第3章 适用场景与典型案例

3.1 用户行为分析

适合:

  • PV/UV;
  • 漏斗;
  • 留存;
  • 用户路径;
  • 广告转化;
  • 实时运营指标。
CREATE TABLE user_behavior
(
    event_time DateTime64(3),
    event_type LowCardinality(String),
    user_id UInt64,
    session_id String,
    page_url String,
    device_type LowCardinality(String),
    country LowCardinality(String),
    stay_duration UInt32,
    extra Map(String, String)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, user_id, event_time);

3.2 日志与可观测性

典型链路:

OpenTelemetry / Fluent Bit / Vector / Filebeat
                ↓
             Kafka/HTTP
                ↓
           ClickHouse
                ↓
       Grafana / ClickStack

适合:

  • Nginx;
  • 应用日志;
  • Trace;
  • Metrics;
  • 安全日志;
  • 审计日志。

3.3 实时数仓

常见分层:

ODS 原始层
   ↓
DWD 明细层
   ↓
DWS 汇总层
   ↓
ADS 应用层

ClickHouse 不一定必须严格复制传统数仓分层。
如果物化视图、宽表和查询直接满足需求,可以适当减少层级。

3.4 不适合的场景

3.4.1 强事务 OLTP

支付、库存、账户余额等核心事务应优先使用事务数据库。

3.4.2 高频小行写入

不要:

每秒数千次单行 INSERT

优先:

批量 INSERT
异步 INSERT
Kafka
批处理

3.4.3 高频点查

例如:

SELECT * FROM user WHERE id = 10001;

如果这是业务系统的核心 QPS,通常不应让 ClickHouse 作为唯一主存储。

3.4.4 JOIN

不要再简单写成“ClickHouse 不适合 JOIN”。

当前 ClickHouse JOIN 能力已经持续增强。
真正需要关注:

  • JOIN 类型;
  • 两侧数据量;
  • build side;
  • 内存;
  • 过滤下推;
  • JOIN 顺序;
  • 是否可以用 Dictionary;
  • 是否应该预计算;
  • 是否需要分布式 JOIN。

第4章 技术架构详解

4.1 单机架构

Client
  ↓
SQL Parser
  ↓
Query Analyzer / Optimizer
  ↓
Query Plan
  ↓
Pipeline
  ↓
Multi-thread / Vectorized Execution
  ↓
MergeTree Parts
  ↓
Disk / Object Storage

查询优化常见方向:

  • Partition pruning;
  • Primary key pruning;
  • PREWHERE;
  • Projection;
  • Skip Index;
  • JOIN 算法;
  • 聚合;
  • LIMIT/ORDER BY 优化。

4.2 Part Merge

INSERT
  ↓
Part A
Part B
Part C
  ↓
Background Merge
  ↓
Larger Part

Merge 的作用包括:

  • 减少 Part 数;
  • 改善数据布局;
  • 应用部分引擎语义;
  • 应用 TTL;
  • 清理已经失效的数据;
  • 提升读取效率。

4.3 分布式架构

Shard

数据水平分片。

Replica

同一 Shard 的副本。

Distributed

逻辑路由表,用于跨 Shard 查询/写入。

CREATE TABLE events_dist
AS events_local
ENGINE = Distributed(
    production_cluster,
    default,
    events_local,
    cityHash64(user_id)
);

分片键不要机械使用 rand()

应考虑:

  • 数据均衡;
  • 查询是否能够命中相关分片;
  • JOIN;
  • 用户/租户隔离;
  • 数据倾斜。

4.4 ClickHouse Keeper

Keeper 主要用于:

  • ReplicatedMergeTree 协调;
  • 分布式元数据;
  • 分布式 DDL;
  • 集群协调。

生产集群通常采用奇数节点,例如 3/5 个 Keeper 节点,以维持 Raft 仲裁能力。

Keeper 不是“ClickHouse 数据库本身”,它是协调组件。


第5章 数据模型设计实战

5.1 数据类型

整数

UInt8   0 ~ 255
UInt16  0 ~ 65535
UInt32  0 ~ 4294967295
UInt64  更大范围

原则:

选择业务真正需要的最小类型,而不是一律 UInt64。

浮点

Float32
Float64

金额不要因为“数据库支持 Float”就直接使用 Float。

Decimal

Decimal(18, 2)
Decimal(38, 6)

适合:

  • 金额;
  • 汇率;
  • 精确计费。

String 与 LowCardinality

status LowCardinality(String)
country LowCardinality(String)

适合稳定低基数字符串。

不要机械地把所有 String 都变成 LowCardinality。

时间

Date
Date32
DateTime
DateTime64

DateTime 可以指定时区:

event_time DateTime('Asia/Shanghai')

因此“DateTime 不带时区信息,需要应用层处理”这一表述不准确。

Array

tags Array(String)

Tuple

geo Tuple(lat Float64, lon Float64)

Map

attributes Map(String, String)

JSON

现代 ClickHouse 应优先了解原生 JSON 类型,而不是继续把 Object('json') 当作主流写法。

概念示例:

CREATE TABLE events
(
    event_time DateTime64(3),
    data JSON
)
ENGINE = MergeTree
ORDER BY event_time;

不同版本对 JSON 的实验开关、参数和语法可能不同,应以实际 26.x 小版本文档为准。

其他

  • UUID
  • IPv4
  • IPv6
  • Enum
  • Nullable
  • Variant
  • Dynamic
  • JSON

5.2 Nullable

不要为了“保险”把所有列都写成:

Nullable(...)

Nullable 会带来额外 NULL 标记及相关处理成本。

但如果 NULL 与 0/空字符串具有明确业务差异,就应该保留 Nullable。

5.3 压缩

推荐先使用默认编码/压缩,再针对实际数据测试。

常见:

user_id UInt64 CODEC(Delta, ZSTD(1))
event_time DateTime64(3) CODEC(DoubleDelta, ZSTD(1))
message String CODEC(ZSTD(3))

不要在没有 benchmark 的情况下强制全表使用高等级 ZSTD。

5.4 完整订单表示例

CREATE DATABASE IF NOT EXISTS ecommerce;

CREATE TABLE ecommerce.orders
(
    order_id UInt64,
    create_time DateTime64(3),
    update_time DateTime64(3),
    buyer_id UInt64,
    seller_id UInt64,
    product_id UInt64,
    product_category LowCardinality(String),
    order_status LowCardinality(String),
    payment_method LowCardinality(String),

    original_price Decimal(18, 2),
    discount_amount Decimal(18, 2),
    final_amount Decimal(18, 2),
    shipping_fee Decimal(18, 2),

    quantity UInt32,

    shipping_province LowCardinality(String),
    shipping_city LowCardinality(String),

    tags Array(String),
    extra_info Map(String, String),

    is_deleted UInt8 DEFAULT 0
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(create_time)
ORDER BY (order_status, create_time, buyer_id);

ORDER BY 只是示例。真实生产表必须根据查询模式重新设计。

5.5 预聚合表

CREATE TABLE ecommerce.orders_hourly
(
    hour DateTime,
    order_status LowCardinality(String),
    product_category LowCardinality(String),
    order_count SimpleAggregateFunction(sum, UInt64),
    total_amount SimpleAggregateFunction(sum, Decimal(18, 2)),
    unique_buyers AggregateFunction(uniq, UInt64)
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(hour)
ORDER BY (order_status, product_category, hour);

第6章 安装部署完全指南

6.1 系统要求

不要在文档中写死“生产必须 32 核/128GB/2TB”。
ClickHouse 的资源需求与:

  • 数据量;
  • 数据增长;
  • 查询并发;
  • 查询复杂度;
  • 副本数;
  • 压缩;
  • Merge;
  • 存储介质

直接相关。

建议

学习环境:

4 CPU+
8~16 GB RAM
SSD

生产环境:

根据压测结果确定,而不是根据固定配置表拍脑袋。

6.2 Linux 安装

优先使用 ClickHouse 官方软件包仓库或官方 Docker 镜像。

不要继续使用:

apt-key adv ...

这属于较旧的 Debian/Ubuntu 密钥管理方式。

安装完成后:

clickhouse-client --query "SELECT version()"

确认版本。

6.3 Docker

学习环境:

docker run -d \
  --name clickhouse \
  --ulimit nofile=262144:262144 \
  -p 8123:8123 \
  -p 9000:9000 \
  clickhouse/clickhouse-server

生产环境必须额外考虑:

  • 数据持久化;
  • 配置持久化;
  • 用户认证;
  • 磁盘;
  • 资源限制;
  • 网络;
  • 备份;
  • 升级。

6.4 网络端口

常见端口:

端口 用途
8123 HTTP
9000 Native TCP
9009 inter-server HTTP,具体用途视配置
9181 Keeper 客户端连接
9444 Keeper Raft 通信示例端口

实际部署时以配置文件为准。

6.5 用户与 RBAC

生产环境不要让业务应用使用无限权限的默认账号。

推荐:

admin        → 管理
etl_user     → 写入
analyst      → 只读
monitor      → 监控

现代 ClickHouse 更推荐使用 SQL RBAC:

CREATE USER analyst IDENTIFIED WITH sha256_password BY 'strong_password';

GRANT SELECT ON analytics.* TO analyst;

实际认证方式与权限模型以部署版本为准。

6.6 集群

基本结构:

             Load Balancer
                    |
        +-----------+-----------+
        |                       |
      Shard 1                 Shard 2
     /       \               /       \
 Replica1  Replica2       Replica1  Replica2
        \       /               \       /
             ClickHouse Keeper

ReplicatedMergeTree:

CREATE TABLE events_local ON CLUSTER production_cluster
(
    event_time DateTime64(3),
    user_id UInt64,
    event_type LowCardinality(String)
)
ENGINE = ReplicatedMergeTree(
    '/clickhouse/tables/{shard}/events',
    '{replica}'
)
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, user_id, event_time);

Distributed:

CREATE TABLE events_dist ON CLUSTER production_cluster
AS events_local
ENGINE = Distributed(
    production_cluster,
    default,
    events_local,
    cityHash64(user_id)
);

ON CLUSTER、Keeper、宏变量、内部复制等配置必须结合实际集群拓扑统一规划,不能简单复制一份 XML 到所有节点。


第7章 SQL 语法与基础操作

7.1 数据库

CREATE DATABASE IF NOT EXISTS analytics;

SHOW DATABASES;

SELECT currentDatabase();

DROP DATABASE IF EXISTS analytics;

7.2 表

CREATE TABLE events
(
    event_time DateTime64(3),
    event_type LowCardinality(String),
    user_id UInt64,
    page_url String,
    duration UInt32
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, user_id, event_time);

SHOW CREATE TABLE events;

DESCRIBE TABLE events;

7.3 INSERT

INSERT INTO events VALUES
('2026-01-01 10:00:00.000', 'click', 1001, '/home', 30),
('2026-01-01 10:01:00.000', 'view', 1002, '/product', 45);

从 SELECT:

INSERT INTO events
SELECT *
FROM events_source
WHERE event_time >= '2026-01-01';

7.4 查询

SELECT
    event_type,
    count() AS cnt,
    uniq(user_id) AS uv
FROM events
WHERE event_time >= now() - INTERVAL 1 DAY
GROUP BY event_type
ORDER BY cnt DESC;

7.5 窗口函数

SELECT
    user_id,
    event_time,
    event_type,
    lag(event_type) OVER (
        PARTITION BY user_id
        ORDER BY event_time
    ) AS prev_event
FROM events;

7.6 JOIN

SELECT
    e.event_time,
    e.event_type,
    u.name
FROM events e
LEFT JOIN users u
    ON e.user_id = u.id;

不要简单地认为“大表 JOIN 大表必然错误”。
需要结合:

  • JOIN 类型;
  • 数据规模;
  • 过滤;
  • 内存;
  • JOIN 算法;
  • 数据排序;
  • 是否可以用 Dictionary;
  • 是否需要预聚合。

7.7 UPDATE / DELETE

旧式 mutation:

ALTER TABLE events
UPDATE page_url = '/new-url'
WHERE page_url = '/old-url';

ALTER TABLE events
DELETE WHERE event_time < '2024-01-01';

现代 ClickHouse 已支持更轻量的 SQL 风格 UPDATE/DELETE 机制,但具体行为受版本和设置影响。

示例:

UPDATE events
SET page_url = '/new-url'
WHERE page_url = '/old-url';

DELETE FROM events
WHERE event_time < '2024-01-01';

注意:

ClickHouse 的 UPDATE/DELETE 即使已经明显增强,也不意味着它变成了传统 OLTP 数据库。高频事务级单行修改仍应谨慎评估。


第8章 高级特性深入

8.1 Incremental Materialized View

物化视图本质上可以理解为 INSERT 触发式的数据转换/预聚合机制。

CREATE MATERIALIZED VIEW events_hourly_mv
TO events_hourly
AS
SELECT
    toStartOfHour(event_time) AS hour,
    event_type,
    count() AS pv,
    uniqState(user_id) AS uv
FROM raw_events
GROUP BY hour, event_type;

注意:

  • 它主要处理进入源表的数据块;
  • 不等于普通 SQL View;
  • 源表历史数据不会因为“创建 MV”自动全部回算;
  • 历史回填需要单独设计。

8.2 Refreshable Materialized View

对于周期性全量/增量重算、外部数据刷新等场景,可以考虑 Refreshable Materialized View。

它与传统 Incremental MV 的思路不同:

Incremental MV
INSERT → 增量处理

Refreshable MV
定时执行 SELECT → 刷新结果

8.3 Dictionary

适合:

事实表 → 维度单值查找

例如:

SELECT
    user_id,
    dictGet('user_dict', 'name', user_id) AS user_name
FROM events;

Dictionary 特别适合 many-to-one / one-to-one 维度映射。

不适合用来替代一对多、多对多 JOIN。

8.4 TTL

删除:

CREATE TABLE logs
(
    event_time DateTime,
    message String
)
ENGINE = MergeTree
ORDER BY event_time
TTL event_time + INTERVAL 90 DAY;

冷热分层:

ALTER TABLE logs
MODIFY TTL
    event_time + INTERVAL 7 DAY TO VOLUME 'cold',
    event_time + INTERVAL 90 DAY DELETE;

TTL 的实际执行依赖后台 Merge,因此不要把 TTL 理解为“到点瞬间删除”。

8.5 Projection

Projection 可以维护同一表的另一种数据组织方式。

ALTER TABLE events
ADD PROJECTION by_user
(
    SELECT
        user_id,
        event_type,
        event_time
    ORDER BY (user_id, event_time)
);

然后:

ALTER TABLE events
MATERIALIZE PROJECTION by_user;

Projection 适合:

  • 同一事实表存在明显不同访问路径;
  • 不希望维护独立汇总表;
  • 查询模式稳定。

但 Projection 会增加存储和写入/后台处理成本。

8.6 Text Index

现代 ClickHouse 已提供原生 Text 倒排索引能力。

示意:

ALTER TABLE logs
ADD INDEX msg_idx message
TYPE text(tokenizer = 'ngrams')
GRANULARITY 1;

相比旧的 tokenbf_v1 / ngrambf_v1,新项目应优先评估原生 text 索引。

8.7 Skip Index

顺序建议:

先优化 ORDER BY
        ↓
再考虑 Projection
        ↓
再考虑 Skip Index

第9章 数据导入与外部集成

9.1 Kafka

典型结构:

Kafka
  ↓
Kafka Engine Table
  ↓
Materialized View
  ↓
MergeTree

示例:

CREATE TABLE kafka_events
(
    event_time DateTime64(3),
    event_type LowCardinality(String),
    user_id UInt64,
    data String
)
ENGINE = Kafka
SETTINGS
    kafka_broker_list = 'kafka1:9092,kafka2:9092',
    kafka_topic_list = 'events',
    kafka_group_name = 'clickhouse_events',
    kafka_format = 'JSONEachRow',
    kafka_num_consumers = 2;

目标表:

CREATE TABLE events
(
    event_time DateTime64(3),
    event_type LowCardinality(String),
    user_id UInt64,
    data String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, user_id, event_time);

物化视图:

CREATE MATERIALIZED VIEW kafka_to_events
TO events
AS
SELECT *
FROM kafka_events;

生产需要关注:

  • Kafka 分区数;
  • consumer 数;
  • ClickHouse 写入批量;
  • 重复消息;
  • offset;
  • 消费延迟;
  • 错误消息;
  • schema 演进;
  • 目标表 Part 数。

9.2 文件

CSV:

clickhouse-client \
  --query="INSERT INTO events FORMAT CSV" < events.csv

JSONEachRow:

clickhouse-client \
  --query="INSERT INTO events FORMAT JSONEachRow" < events.json

Parquet:

clickhouse-client \
  --query="INSERT INTO events FORMAT Parquet" < events.parquet

9.3 S3

查询:

SELECT *
FROM s3(
    'https://bucket.s3.amazonaws.com/events/*.parquet',
    'access_key',
    'secret_key',
    'Parquet'
);

生产环境优先考虑:

  • IAM Role;
  • 临时凭证;
  • 对象存储权限最小化;
  • 分区路径;
  • Parquet;
  • 文件大小;
  • 并行读取。

9.4 MySQL / PostgreSQL

可以使用:

  • mysql() / MySQL 表引擎;
  • postgresql() / PostgreSQL 表引擎;
  • CDC;
  • Debezium + Kafka;
  • ClickPipes(ClickHouse Cloud 场景)。

不要使用:

CREATE MATERIALIZED VIEW mysql_users_mv AS
SELECT * FROM mysql_users;

来误解为“它可以自动把 MySQL UPDATE 增量同步到 ClickHouse”。

真正的 CDC 需要变更事件来源和状态处理机制。

9.5 HDFS

SELECT *
FROM hdfs(
    'hdfs://namenode:9000/path/*.parquet',
    'Parquet'
);

9.6 导出

文件:

INSERT INTO FUNCTION file(
    'output.csv',
    'CSV',
    'id UInt64, name String'
)
SELECT id, name
FROM users;

S3:

INSERT INTO FUNCTION s3(
    'https://bucket.s3.amazonaws.com/output/*.parquet',
    'access_key',
    'secret_key',
    'Parquet'
)
SELECT *
FROM events;

第10章 生产环境运维与监控

10.1 重要系统表

查询

SELECT
    query_id,
    query_start_time,
    query_duration_ms,
    read_rows,
    read_bytes,
    memory_usage,
    query
FROM system.query_log
WHERE type = 'QueryFinish'
ORDER BY query_duration_ms DESC
LIMIT 20;

Part

SELECT
    database,
    table,
    partition,
    count() AS parts
FROM system.parts
WHERE active = 1
GROUP BY database, table, partition
ORDER BY parts DESC;

Merge

SELECT *
FROM system.merges;

Mutation

SELECT
    database,
    table,
    mutation_id,
    command,
    create_time,
    parts_to_do,
    is_done
FROM system.mutations
WHERE is_done = 0;

Replication

SELECT
    database,
    table,
    is_leader,
    is_readonly,
    total_replicas,
    active_replicas,
    absolute_delay
FROM system.replicas;

10.2 Prometheus

ClickHouse 可以暴露 Prometheus 指标。

示意:

<clickhouse>
    <prometheus>
        <endpoint>/metrics</endpoint>
        <port>9363</port>
        <metrics>true</metrics>
        <events>true</events>
        <asynchronous_metrics>true</asynchronous_metrics>
    </prometheus>
</clickhouse>

Prometheus:

scrape_configs:
  - job_name: clickhouse
    static_configs:
      - targets:
          - ch-node1:9363
          - ch-node2:9363
          - ch-node3:9363

指标名称和标签可能随版本、导出方式和部署配置变化,不应把某一套指标名当作永久 API。

10.3 告警维度

建议监控:

查询:
- P50/P95/P99 延迟
- 慢查询数量
- 失败查询

写入:
- INSERT QPS
- 写入延迟
- 写入失败
- Kafka lag

存储:
- 磁盘使用率
- Parts
- Merge backlog
- TTL backlog

集群:
- Replica delay
- Readonly replica
- Keeper 健康
- Shard 不均衡

资源:
- CPU
- RAM
- Disk I/O
- Network

不要只设置:

Parts > 10000

就认定集群一定故障。

10.4 运维命令

SYSTEM FLUSH LOGS;

SYSTEM SYNC REPLICA events;

SYSTEM RESTART REPLICA events;

KILL QUERY WHERE query_id = 'xxx';

OPTIMIZE TABLE ... FINAL 应谨慎使用:

OPTIMIZE TABLE events FINAL;

不要把手动 FINAL 当成日常“保养”。


第11章 可视化与可观测性:Grafana、ClickStack 等

11.1 Grafana

ClickHouse 有成熟的 Grafana 集成。

典型结构:

ClickHouse
    ↓
Grafana ClickHouse Plugin
    ↓
Dashboard

Grafana 查询应尽量:

  • 限定时间范围;
  • 只读取需要的列;
  • 避免无条件全表扫描;
  • 对 Top N、P95、UV 等指标做合理预聚合。

示例:

SELECT
    toStartOfMinute(event_time) AS time,
    count() AS pv,
    uniq(user_id) AS uv
FROM events
WHERE event_time >= $__fromTime
  AND event_time <= $__toTime
GROUP BY time
ORDER BY time;

具体宏名称应以当前 ClickHouse Grafana 插件版本为准。

11.2 ClickStack

现代 ClickHouse 可观测性生态已经不应只围绕第三方 ckibana 展开。

ClickStack 是 ClickHouse 面向 logs、traces、metrics 的可观测性产品形态,
适合需要原生 ClickHouse 可观测性体验的场景。

推荐理解:

OpenTelemetry
     ↓
ClickHouse
     ↓
ClickStack / Grafana

11.3 关于 ckibana

由同城旅行开源的kibana代理

如果已有历史系统依赖 ckibana,应单独验证:

  • 项目维护状态;
  • Kibana 版本兼容;
  • DSL 覆盖率;
  • 聚合语义;
  • 安全认证;
  • 升级路径。

第12章 性能调优实战

12.1 性能优化优先级

推荐顺序:

1. 查询是否扫描太多数据
        ↓
2. ORDER BY / PRIMARY KEY 是否合理
        ↓
3. Partition 是否合理
        ↓
4. 数据类型是否合理
        ↓
5. Projection / Materialized View
        ↓
6. JOIN / GROUP BY
        ↓
7. Skip Index
        ↓
8. 参数调优
        ↓
9. 硬件扩容

12.2 EXPLAIN

EXPLAIN indexes = 1
SELECT count()
FROM events
WHERE tenant_id = 1001
  AND event_time >= now() - INTERVAL 1 DAY;

还可以:

EXPLAIN PLAN
SELECT ...;
EXPLAIN PIPELINE
SELECT ...;

12.3 PREWHERE

宽表中,如果过滤条件能显著减少读取列,可以关注 PREWHERE:

SELECT
    user_id,
    event_type,
    payload
FROM events
PREWHERE event_type = 'click'
WHERE event_time >= now() - INTERVAL 1 DAY;

实际是否有收益应通过执行计划和读字节验证。

12.4 聚合

精确去重:

SELECT uniqExact(user_id)
FROM events;

近似高性能:

SELECT uniq(user_id)
FROM events;

分位数:

SELECT
    quantile(0.50)(duration) AS p50,
    quantile(0.95)(duration) AS p95,
    quantile(0.99)(duration) AS p99
FROM events;

大聚合需要关注:

  • group key 基数;
  • 内存;
  • 外部聚合;
  • 预聚合;
  • 结果集大小。

12.5 JOIN

优化顺序:

先过滤
↓
减少参与 JOIN 的数据
↓
选择合适 JOIN 算法
↓
确认 build side
↓
评估 Dictionary / 宽表 / MV

不要把“小表放右边”当作永远正确的性能定律。
应以当前版本的 JOIN planner、算法和真实执行计划为准。

12.6 写入

不推荐:

1 行 INSERT × 10000 次

推荐:

批量 INSERT

或者:

async_insert
Kafka
S3
批处理

Too Many Parts

重点解决:

  • 批量写;
  • 降低小 INSERT 频率;
  • 避免过细分区;
  • 检查 Merge 资源;
  • 检查磁盘 I/O;
  • 检查数据是否严重倾斜。

不要第一时间把:

<background_pool_size>32</background_pool_size>

写死到所有生产环境。

增加后台线程可能加剧 CPU、磁盘竞争。

12.7 压缩

查看:

SELECT
    database,
    table,
    column,
    data_compressed_bytes,
    data_uncompressed_bytes,
    round(
        data_uncompressed_bytes / nullIf(data_compressed_bytes, 0),
        2
    ) AS ratio
FROM system.parts_columns
WHERE active = 1
  AND database = 'default'
  AND table = 'events';

12.8 冷热分层

典型:

SSD / 本地盘
   ↓
Hot

对象存储 / 大容量盘
   ↓
Cold

TTL
   ↓
自动迁移/删除

冷热策略必须考虑:

  • 查询频率;
  • 网络带宽;
  • 对象存储延迟;
  • Merge;
  • 备份;
  • 恢复时间。

第13章 备份恢复与灾备方案

13.1 备份原则

必须区分:

副本 ≠ 备份

Replica 解决:

  • 节点故障;
  • 读扩展;
  • 高可用。

Backup 解决:

  • 误删;
  • 错误 UPDATE/DELETE;
  • 数据损坏;
  • 灾难恢复;
  • 长期归档。

13.2 BACKUP / RESTORE

现代 ClickHouse 推荐优先评估原生:

BACKUP TABLE events
TO Disk('backups', 'events_backup');

RESTORE TABLE events
FROM Disk('backups', 'events_backup');

也支持数据库级备份,以及基于 base_backup 的增量备份能力,具体语法和存储目标以版本为准。

13.3 FREEZE

FREEZE 仍可用于特定传统备份流程,但不应作为唯一的现代备份方案。

ALTER TABLE events
FREEZE WITH NAME 'backup_20260101';

然后将 shadow 中的数据复制到独立存储。

13.4 S3/对象存储备份

生产备份建议:

ClickHouse
   ↓
BACKUP
   ↓
对象存储
   ↓
跨区域/不可变存储

必须考虑:

  • 加密;
  • IAM;
  • 生命周期;
  • 版本控制;
  • WORM/不可变策略;
  • 恢复权限;
  • 恢复速度。

13.5 RPO/RTO

上线前必须定义:

RPO = 最多允许丢多少数据
RTO = 最多允许多长时间恢复

例如:

RPO:5 分钟
RTO:30 分钟

然后反推:

  • Replica;
  • Backup 频率;
  • 跨地域;
  • Kafka 保留时间;
  • 对象存储;
  • 恢复脚本。

13.6 灾备演练

必须定期验证:

1. 停掉一个 Replica
2. 查询是否继续
3. 检查 replication_queue
4. 恢复节点
5. 等待同步
6. 验证数据一致性
7. 模拟误删
8. 从 Backup 恢复
9. 记录 RTO

不要只验证“备份文件存在”,必须验证“真的能恢复”。

第15章 常见问题与避坑指南

15.1 Too Many Parts

现象:

Too many parts

排查:

SELECT
    database,
    table,
    partition,
    count() AS parts
FROM system.parts
WHERE active = 1
GROUP BY database, table, partition
ORDER BY parts DESC;

解决:

  • 批量写入;
  • 异步 INSERT;
  • 合理分区;
  • 检查 Merge;
  • 检查磁盘;
  • 避免无意义手工 OPTIMIZE。

15.2 内存溢出

排查:

SELECT
    query_id,
    query_duration_ms,
    memory_usage,
    read_rows,
    read_bytes,
    query
FROM system.query_log
WHERE type = 'QueryFinish'
ORDER BY memory_usage DESC
LIMIT 20;

常见原因:

  • 大 JOIN;
  • 高基数 GROUP BY;
  • ORDER BY 大结果集;
  • uniqExact
  • SELECT *;
  • 无时间过滤。

15.3 副本延迟

SELECT
    database,
    table,
    is_readonly,
    total_replicas,
    active_replicas,
    absolute_delay
FROM system.replicas;

进一步:

SELECT *
FROM system.replication_queue
ORDER BY create_time;

15.4 查询慢

按顺序排查:

1. read_rows
2. read_bytes
3. selected_parts
4. selected_marks/granules
5. primary key
6. partition pruning
7. JOIN
8. GROUP BY
9. memory
10. disk I/O

15.5 排序键错误

错误思路:

ORDER BY (event_time, user_id, event_type)

并不是因为“时间不能放第一”而错误,而是:

如果真实查询主要按 user_id/event_type 过滤,且 event_time 作为范围条件,那么这种排序可能无法充分利用前缀索引。

正确做法:

分析查询 → 设计候选 ORDER BY → EXPLAIN → 压测 → 选型

15.6 分区错误

不要死记:

按天 = 错
按月 = 对

应该判断:

分区数量
每分区数据量
写入频率
生命周期
查询裁剪
运维需求

15.7 过度使用 FINAL

FINAL 是解决特定引擎语义问题的工具,不是通用性能优化手段。

尤其在大表上频繁:

SELECT ... FROM replacing_table FINAL;

可能产生明显额外开销。

15.8 过度使用 Nullable

如果 NULL 有明确业务含义,应使用 Nullable。

否则不要为了“字段安全”全部 Nullable。

15.9 把 Replica 当 Backup

错误:

2 副本 = 备份

正确:

Replica + Backup + 灾备演练

15.10 把 ClickHouse 当 MySQL

错误:

所有业务表都迁移到 ClickHouse

正确:

OLTP → MySQL/PostgreSQL
OLAP → ClickHouse

两者可以通过 CDC/ETL 协同。

15.11 生产环境检查清单

☐ 数据模型已评审
☐ ORDER BY 已通过真实查询验证
☐ Partition 已验证
☐ INSERT 批量策略已确定
☐ Too Many Parts 风险已评估
☐ Keeper 集群已验证
☐ Replica 同步正常
☐ RBAC 已配置
☐ 默认账号安全策略已完成
☐ 磁盘告警已配置
☐ CPU/RAM/I/O 告警已配置
☐ Query Log 已接入
☐ 慢查询告警已配置
☐ Kafka/S3 数据链路有监控
☐ TTL 已验证
☐ Backup 已实施
☐ Restore 已演练
☐ RPO/RTO 已定义
☐ 升级/回滚方案已准备
☐ 灾备切换已演练
☐ 版本升级策略已建立
☐ 运维文档已完成

第16章 CKibana:正确定位、架构、限制与生产使用

16.1 CKibana 到底是什么?

CKibana 不是 ClickHouse 官方数据库引擎,也不是 ClickHouse 内置的“可视化模块”。

更准确的定义是:

Kibana
   ↓ Elasticsearch API / Kibana 请求
CKibana
   ↓ 将请求转换为 ClickHouse 查询
ClickHouse
   ↓
返回结果
   ↓
CKibana 模拟 Elasticsearch 响应
   ↓
Kibana 展示

CKibana 官方文档直接将其定义为:

ClickHouse adapter/proxy for Kibana

它的目标是让用户继续使用原生 Kibana UI 来查询和分析 ClickHouse 中的数据。

16.2 ckibana 架构

                       ┌────────────────────┐
                       │      Kibana        │
                       │ Discover / Lens /  │
                       │ Dashboard / Search │
                       └─────────┬──────────┘
                                 │
                         Elasticsearch API
                                 │
                                 ▼
                       ┌────────────────────┐
                       │      CKibana       │
                       │ API Proxy /        │
                       │ Query Translator   │
                       └──────┬───────┬─────┘
                              │       │
                     ClickHouse      Elasticsearch
                       查询数据       元数据/缓存
                              │       │
                              ▼       ▼
                       ┌────────────────────┐
                       │    ClickHouse      │
                       │   日志/指标数据     │
                       └────────────────────┘

官方 CKibana 使用文档明确说明:

  • Kibana 用于 UI;
  • Elasticsearch 用于 Kibana 元数据及查询缓存等能力;
  • ClickHouse 保存真实业务/日志数据;
  • CKibana 提供 Proxy 和语法转换;
  • Kibana 的 elasticsearchHosts 指向 CKibana Proxy;
  • ClickHouse 表需要配置对应的 index whitelist。

16.3 CKibana 是否等于 Elasticsearch?

不是。

CKibana 的目标是兼容 Kibana 常见查询请求,并把请求转换到 ClickHouse。

因此:

Kibana
≠ Elasticsearch

CKibana
≠ Elasticsearch

ClickHouse
≠ Elasticsearch

CKibana 是一个兼容适配层。

官方文档说明其支持 Elasticsearch/Kibana 6.x、7.x、8.x 的相关版本,并支持常见 Elasticsearch 语法,
但并不是“100% Elasticsearch DSL 完整兼容”。部分语法有明确限制。

用户可以继续使用原生 Kibana 的大量查询、Discover、Dashboard 等交互能力,
但底层数据已经由 ClickHouse 提供,具体功能是否可用取决于 CKibana 对对应
Elasticsearch/Kibana API、查询语法和数据类型的支持程度。

官方文档还列出了当前 TODO/限制,例如部分 Kibana 8.x 场景下 JSON、Doc View、
Single document、Surrounding documents 等能力仍存在限制。

16.5 原文 CKibana 安装方式的问题

当前 CKibana 官方文档的本地运行方式是:

JDK 17+
    ↓
CKibana Java 应用
    ↓
java -jar ckibana.jar

官方文档说明 CKibana 本地运行需要 JDK 17 或更高版本,配置文件路径为:

src/main/resources/application.yml

并不是原文中的自定义 ckibana.yaml 配置模型。

因此原文中:

Linux amd64 二进制
systemd
ckibana.yaml
listen: :8000

都应该从“官方标准安装步骤”中删除,或者明确标记为某个历史/二次打包版本的部署方式

16.6 CKibana 快速体验

官方提供 Docker Compose 快速体验方式:

cd ckibana/docker-compose
docker-compose up -d

官方 Quick Start 会准备 Kibana、CKibana、ClickHouse 等测试环境,并提供示例配置。

16.7 正确的配置逻辑

第一步:准备 ClickHouse 表

例如:

CREATE DATABASE IF NOT EXISTS ops;

CREATE TABLE ops.nginx_logs
(
    `@timestamp` DateTime64(3),
    hostname LowCardinality(String),
    remote_addr String,
    request_method LowCardinality(String),
    request_uri String,
    status UInt16,
    body_bytes_sent UInt64,
    request_time Float32,
    message String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(`@timestamp`)
ORDER BY (`@timestamp`, hostname);

第二步:配置 CKibana 的 ClickHouse

官方文档提供通过配置接口更新 ClickHouse 连接信息的方式,例如:

curl --location --request POST \
'http://localhost:8080/config/updateCk?url=ckUrl&user=default&pass=default&defaultCkDatabase=ops'

实际参数和部署方式应以当前 CKibana 文档/代码为准。

第三步:配置 Index Whitelist

例如:

curl --location --request POST \
'http://localhost:8080/config/updateWhiteIndexList?list=nginx_logs'

官方文档明确要求 index pattern 与 ClickHouse 表名匹配,并且表名需要进入 whitelist。

第四步:让 Kibana 指向 CKibana

概念上:

elasticsearchHosts:
  - http://ckibana-host:8080

具体配置键名称随 Kibana 版本变化,部署时应以对应 Kibana 版本文档为准。

第五步:创建 Index Pattern

例如 ClickHouse 表:

ops.nginx_logs

则 Kibana Index Pattern:

nginx_logs

官方文档强调 index pattern 与 CKibana 中 ClickHouse 表名需要匹配。

16.8 CKibana 的时间字段识别

官方 troubleshooting 文档说明:

  • Date 类型字段,例如 DateTime64,可以被识别为时间字段;
  • 某些情况下字段名包含 time 也会参与识别。

因此推荐日志表直接设计明确时间字段:

event_time DateTime64(3)

而不是:

timestamp UInt64

再依赖适配层猜测。

16.9 CKibana 的性能问题

CKibana 本质上还是:

Kibana 查询
   ↓
转换
   ↓
ClickHouse 查询

所以如果 Kibana 面板设计成:

过去 30 天
+
高基数 GROUP BY
+
大量聚合
+
大量日志明细
+
多个 Panel 并发刷新

最终瓶颈依然会落在 ClickHouse 查询上。

CKibana 官方 troubleshooting 也建议通过 ClickHouse system.query_log 分析 CPU 高使用率,
重点关注 read_rowsread_bytes,并可以将有问题的 SQL 加入 CKibana blacklist 或调整对应图表。


第17章 ClickHouse 表引擎全景

学习建议:不要把“所有引擎”理解为每一个都需要在生产项目中使用。

ClickHouse 的表引擎可以按照“存储/合并语义、查询辅助结构、数据源/集成、外部存储”等分类。
生产学习重点应放在 MergeTree 家族,再掌握 Distributed、Kafka、Buffer、Dictionary、Join 等特殊引擎。

17.1 第一类:MergeTree 核心家族

引擎 核心作用 典型场景 学习优先级
MergeTree 普通明细数据 日志、事件、事实表 ★★★★★
ReplacingMergeTree 去重/最新版本 CDC、快照、最终状态 ★★★★★
SummingMergeTree 数值合并 简单指标汇总 ★★★★
AggregatingMergeTree 保存聚合状态 UV、AVG、分位数、复杂预聚合 ★★★★★
CollapsingMergeTree Sign 折叠 特殊更新/取消模型 ★★★
VersionedCollapsingMergeTree 带版本折叠 乱序更新场景 ★★★
GraphiteMergeTree Graphite 指标聚合 Graphite 时间序列 ★★
CoalescingMergeTree 稀疏字段更新合并 IoT/状态快照/部分更新 ★★★★

其中 CoalescingMergeTree 是现代版本的重要补充,25.6 引入,用于将同一实体的稀疏更新逐渐合并成更完整的状态,
与 ReplacingMergeTree 的“整行替换”思路不同。

17.2 MergeTree 选择口诀

追加明细
   ↓
MergeTree

需要去重/最新版本
   ↓
ReplacingMergeTree

只需要简单数值累加
   ↓
SummingMergeTree

需要保存 uniq / avg / quantile 等复杂聚合状态
   ↓
AggregatingMergeTree

稀疏字段更新
   ↓
CoalescingMergeTree

特殊 Sign 折叠模型
   ↓
Collapsing / VersionedCollapsing

17.3 第二类:查询/辅助型表引擎

Join

用于预构建 JOIN 数据结构。

适合:

小型/维度数据
       ↓
Join Engine
       ↓
JOIN

但现代 ClickHouse 中,Dictionary、Join Table、普通 JOIN、预计算宽表都应该根据实际场景比较。
ClickHouse Cloud 的 Join table 已经可以用 MergeTree 家族作为持久化后端。

Dictionary

严格来说 Dictionary 是一套外部/内存维度数据访问机制,不应与普通事实表简单等同。

典型:

SELECT
    user_id,
    dictGet('user_dict', 'name', user_id)
FROM events;

Set

用于集合型数据结构和 IN 查询等场景。

17.4 第三类:数据摄入/缓冲引擎

引擎 用途
Kafka Kafka 消费
RabbitMQ RabbitMQ 消费
S3Queue 从对象存储持续消费文件
Buffer 小写入缓冲
Null 接收数据但不保存
File 文件数据源/目标相关场景
URL HTTP 数据源
PostgreSQL PostgreSQL 数据访问
MySQL MySQL 数据访问
MongoDB MongoDB 数据访问
HDFS HDFS 数据访问

外部引擎是否可用、参数如何配置,与 ClickHouse 小版本及部署形态有关。

17.5 第四类:内存/临时型引擎

常见:

Memory
Buffer
Set
Join

这些引擎不能与 MergeTree 的持久化语义混为一谈。

17.6 第五类:日志型历史引擎

包括:

Log
TinyLog
StripeLog

这些适合特殊/简单场景。

生产大规模分析表通常优先考虑 MergeTree 家族,而不是因为“简单”就选择 TinyLog。


第18章 ClickHouse 各种 View 全面理解

18.1 普通 View

普通 View:

CREATE VIEW v_events AS
SELECT
    event_type,
    count() AS cnt
FROM events
GROUP BY event_type;

查询:

SELECT *
FROM v_events;

核心特点:

View 本身不保存查询结果
        ↓
每次 SELECT
        ↓
重新执行定义 SQL

所以 View:

  • 适合封装复杂 SQL;
  • 适合统一语义;
  • 不等于缓存;
  • 不等于物化。

18.2 Materialized View:增量物化视图

典型:

INSERT raw_events
       ↓
Incremental Materialized View
       ↓
summary_table

它更像:

INSERT Trigger + SELECT Transform

而不是传统数据库意义上的“永久缓存查询结果”。

官方明确说明 Incremental MV 只针对新插入的数据块执行,
不会自动感知源表之后的 Merge、Mutation、DROP PARTITION 等变化。

18.3 Refreshable Materialized View

Refreshable MV:

CREATE MATERIALIZED VIEW daily_report
REFRESH EVERY 1 HOUR
AS
SELECT ...
FROM ...
GROUP BY ...;

它更接近:

定时执行完整/复杂 SELECT
        ↓
刷新结果

适合:

  • 周期性报表;
  • 多表 JOIN;
  • 全量重算;
  • 外部 API/外部数据库周期刷新;
  • 不适合实时增量触发的复杂计算。

ClickHouse 官方明确区分 Incremental 和 Refreshable 两种 MV。

18.4 APPEND 模式

Refreshable MV 还支持 APPEND 模式:

刷新结果
   ↓
不是覆盖
   ↓
而是追加到目标

适合周期性累积结果的场景。

18.5 Projection 不是 View

Projection:

同一张表
   ├── 原始排序布局
   └── Projection 排序布局

它不是:

CREATE VIEW

也不是:

Materialized View

Projection 更像是同一张表的额外物理访问路径


第19章 ClickHouse 聚合函数完整体系

聚合函数不要只记 count/sum/avg。生产分析真正常用的是:

基础聚合 + 条件聚合 + 去重 + 极值取整行 + 数组聚合 + TopK + 分位数 + Bitmap + 聚合状态。

19.1 基础聚合

count()
countIf(condition)

sum(x)
sumIf(x, condition)

avg(x)
avgIf(x, condition)

min(x)
max(x)

minIf(x, condition)
maxIf(x, condition)

示例:

SELECT
    count() AS orders,
    sum(amount) AS revenue,
    avg(amount) AS avg_amount,
    min(amount) AS min_amount,
    max(amount) AS max_amount
FROM orders;

19.2 去重聚合

uniq

uniq(user_id)

适合高性能近似 UV。

uniqExact

uniqExact(user_id)

精确去重,但资源消耗可能明显更高。

uniqCombined / uniqCombined64 / uniqHLL12

用于不同精度、内存和性能平衡。

学习重点:

uniq
uniqExact
uniqCombined
uniqHLL12

19.3 条件聚合

countIf(status = 'success')

sumIf(amount, status = 'success')

uniqIf(user_id, event_type = 'purchase')

比:

sum(if(status = 'success', amount, 0))

通常更直观。

19.4 argMax / argMin

这是 ClickHouse 非常重要的一类函数。

例如:

SELECT
    user_id,
    argMax(status, update_time) AS latest_status,
    max(update_time) AS latest_time
FROM user_status
GROUP BY user_id;

意思:

找到 update_time 最大的那一行
取这一行的 status

它经常用于:

ReplacingMergeTree 查询
CDC
最新状态
维度快照

19.5 数组聚合

groupArray(x)

groupArrayIf(x, condition)

groupUniqArray(x)

groupArraySorted(N)(x)

示例:

SELECT
    user_id,
    groupArray(event_type) AS path
FROM events
GROUP BY user_id;

19.6 TopK

topK(10)(page_url)

适合:

Top N
热门商品
热门 URL
热门关键词

19.7 分位数

精确/普通分位数

quantile(0.5)(duration)
quantile(0.95)(duration)
quantile(0.99)(duration)

TDigest

quantileTDigest(0.95)(duration)

高精度/性能场景

可进一步学习:

quantileExact
quantileTDigest
quantileTiming
quantileBFloat16

生产日志/链路监控中:

P50
P90
P95
P99
P999

非常常见。

19.8 Bitmap

典型:

用户圈选
UV
人群交集
人群并集
留存

相关函数:

bitmapBuild
bitmapCardinality
bitmapAnd
bitmapOr
bitmapXor
bitmapAndCardinality
bitmapOrCardinality

适合大规模集合运算。

19.9 统计聚合

可以进一步学习:

corr
covarPop
covarSamp
varPop
varSamp
stddevPop
stddevSamp
skewSamp
kurtSamp

19.10 时间序列聚合

可以结合:

groupArray
groupArraySorted
timeSeries*
histogram
quantile*

用于:

  • 时间序列;
  • 延迟分布;
  • 直方图;
  • 事件路径。

第20章 AggregateFunction、State、Merge:物化汇总表的核心

这是原文最需要加强的部分之一。

20.1 为什么需要 AggregateFunction?

普通:

avg(duration)

最终返回:

Float64

但是如果要把“未来还可以继续合并的平均值状态”保存下来,就不能简单保存最终平均数。

因为:

avg(A + B)

不能简单:

(avg(A) + avg(B)) / 2

所以 ClickHouse 保存的是:

Aggregate State

20.2 State

例如:

avgState(duration)

返回的是:

AggregateFunction(avg, ...)

状态。

例如:

uniqState(user_id)

不是最终 UV 数字,而是:

可以继续 Merge 的 uniq 状态

20.3 Merge

查询时:

uniqMerge(uv_state)

把多个状态合并成最终 UV。

例如:

SELECT
    uniqMerge(uv_state) AS uv
FROM daily_user_stats;

20.4 SimpleAggregateFunction

对于某些满足简单可合并语义的聚合,可以使用:

SimpleAggregateFunction(sum, UInt64)
SimpleAggregateFunction(min, UInt64)
SimpleAggregateFunction(max, UInt64)

例如:

CREATE TABLE daily_stats
(
    day Date,
    pv SimpleAggregateFunction(sum, UInt64),
    max_duration SimpleAggregateFunction(max, UInt32)
)
ENGINE = AggregatingMergeTree
ORDER BY day;

它和:

AggregateFunction(...)

不是一回事。

简化理解

SimpleAggregateFunction
    ↓
结果本身就可以继续合并

AggregateFunction
    ↓
保存真正的聚合状态

20.5 AggregatingMergeTree 的完整示例

原始表

CREATE TABLE raw_events
(
    event_time DateTime64(3),
    user_id UInt64,
    event_type LowCardinality(String),
    amount Decimal(18, 2),
    duration UInt32
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, event_time, user_id);

汇总表

CREATE TABLE event_hourly
(
    hour DateTime,
    event_type LowCardinality(String),

    pv UInt64,
    uv AggregateFunction(uniq, UInt64),
    revenue AggregateFunction(sum, Decimal(18, 2)),
    avg_duration AggregateFunction(avg, UInt32),
    p95_duration AggregateFunction(quantileTDigest(0.95), UInt32),
    max_duration SimpleAggregateFunction(max, UInt32)
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(hour)
ORDER BY (event_type, hour);

物化视图

CREATE MATERIALIZED VIEW event_hourly_mv
TO event_hourly
AS
SELECT
    toStartOfHour(event_time) AS hour,
    event_type,

    count() AS pv,
    uniqState(user_id) AS uv,
    sumState(amount) AS revenue,
    avgState(duration) AS avg_duration,
    quantileTDigestState(0.95)(duration) AS p95_duration,
    max(duration) AS max_duration
FROM raw_events
GROUP BY
    hour,
    event_type;

20.6 正确查询汇总表

SELECT
    hour,
    event_type,

    sum(pv) AS pv,
    uniqMerge(uv) AS uv,
    sumMerge(revenue) AS revenue,
    avgMerge(avg_duration) AS avg_duration,
    quantileTDigestMerge(0.95)(p95_duration) AS p95_duration,
    max(max_duration) AS max_duration

FROM event_hourly
WHERE hour >= now() - INTERVAL 7 DAY
GROUP BY
    hour,
    event_type
ORDER BY hour;

这是学习 AggregatingMergeTree 时必须掌握的完整闭环:

raw_events
   ↓
Materialized View
   ↓
State
   ↓
AggregatingMergeTree
   ↓
Merge
   ↓
最终查询

20.7 为什么不能直接 SELECT avg?

错误:

SELECT
    avg(avg_duration)
FROM event_hourly;

如果 avg_duration 是:

AggregateFunction(avg, UInt32)

正确:

avgMerge(avg_duration)

20.8 为什么不能直接 SELECT uniq?

错误:

SELECT
    uniq(uv)
FROM event_hourly;

正确:

uniqMerge(uv)

因为:

uv

是状态,不是原始 user_id。

20.9 多天汇总表的正确查询

例如:

hourly
daily

如果 daily 本身也是聚合状态,可以继续 Merge。

SELECT
    toDate(day) AS day,
    uniqMerge(uv) AS uv,
    sumMerge(revenue) AS revenue
FROM event_daily
WHERE day >= today() - 30
GROUP BY day
ORDER BY day;

第21章 物化视图 + 汇总表:完整生产设计

21.1 最推荐的思维模型

               原始明细表
                   │
                   │ INSERT
                   ▼
        ┌─────────────────────┐
        │ Incremental MV      │
        └──────────┬──────────┘
                   ▼
             汇总表 DWS
                   │
          ┌────────┼────────┐
          ▼        ▼        ▼
       Grafana    BI       API

21.2 不要设计成无限 MV 链

原文有:

raw
 ↓
minute MV
 ↓
hour MV
 ↓
day MV

这并不是一定错误,但要非常谨慎。

因为每增加一个 Incremental MV:

INSERT raw
   ↓
多个 MV 同时执行
   ↓
CPU / Memory / Write Amplification 增加

ClickHouse 官方也特别提醒 Materialized View sprawl 和多 MV 带来的写入放大问题。

21.3 推荐的设计

如果原始数据量不是极端大:

raw
 ↓
一个合理粒度的汇总表

例如:

raw → hourly

查询:

hourly → GROUP BY day

而不是:

raw → minute → hour → day → week

除非真实查询和数据规模证明这样做有价值。

21.4 汇总表到底选什么引擎?

简单 sum/count

可以:

SummingMergeTree

UV / AVG / P95

优先:

AggregatingMergeTree

最新状态

可以:

ReplacingMergeTree

稀疏字段状态

考虑:

CoalescingMergeTree

21.5 汇总表应该怎么查?

情况 A:SummingMergeTree

表:

CREATE TABLE sales_daily
(
    day Date,
    product_id UInt64,
    order_count UInt64,
    revenue Decimal(18, 2)
)
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(day)
ORDER BY (product_id, day);

查询:

SELECT
    day,
    product_id,
    sum(order_count) AS orders,
    sum(revenue) AS revenue
FROM sales_daily
WHERE day >= today() - 30
GROUP BY
    day,
    product_id
ORDER BY day;

不要因为“后台会自动求和”就完全省略 sum()

21.6 情况 B:AggregatingMergeTree

表:

CREATE TABLE sales_daily_agg
(
    day Date,
    product_id UInt64,
    order_count SimpleAggregateFunction(sum, UInt64),
    revenue AggregateFunction(sum, Decimal(18, 2)),
    uv AggregateFunction(uniq, UInt64),
    avg_price AggregateFunction(avg, Decimal(18, 2)),
    p95_latency AggregateFunction(quantileTDigest(0.95), UInt32)
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(day)
ORDER BY (product_id, day);

查询:

SELECT
    day,
    product_id,
    sum(order_count) AS orders,
    sumMerge(revenue) AS revenue,
    uniqMerge(uv) AS uv,
    avgMerge(avg_price) AS avg_price,
    quantileTDigestMerge(0.95)(p95_latency) AS p95_latency
FROM sales_daily_agg
WHERE day >= today() - 30
GROUP BY
    day,
    product_id
ORDER BY day;

21.7 情况 C:ReplacingMergeTree

如果保存的是最新订单状态:

SELECT
    order_id,
    argMax(status, version) AS latest_status,
    argMax(amount, version) AS latest_amount
FROM orders
GROUP BY order_id;

或者:

SELECT *
FROM orders FINAL;

二者都可以解决某些最终状态读取问题,但:

FINAL

通常更直接,而:

argMax

在很多分析场景中更灵活。

ClickHouse 官方也推荐理解 FINALargMax 的权衡。


第22章 聚合函数 × 引擎 × 物化视图对照表

业务指标 原始查询函数 汇总表字段 MV 写入 查询方式
PV count() UInt64/SimpleAggregateFunction(sum) count() sum()
GMV sum(amount) AggregateFunction(sum, Decimal) sumState(amount) sumMerge()
UV uniq(user_id) AggregateFunction(uniq, UInt64) uniqState() uniqMerge()
精确 UV uniqExact(user_id) AggregateFunction(uniqExact, UInt64) uniqExactState() uniqExactMerge()
AVG avg(x) AggregateFunction(avg, T) avgState(x) avgMerge()
MAX max(x) SimpleAggregateFunction(max, T) max(x) max()
MIN min(x) SimpleAggregateFunction(min, T) min(x) min()
P95 quantile(0.95)(x) AggregateFunction(...) quantileState quantileMerge
P99 TDigest quantileTDigest(0.99)(x) AggregateFunction(...) quantileTDigestState quantileTDigestMerge
TopK topK(10)(x) AggregateFunction(topK(10), T) topKState topKMerge
用户路径 groupArray(x) AggregateFunction(groupArray, T) groupArrayState groupArrayMerge
条件 PV countIf() SimpleAggregateFunction(sum, UInt64) countIf sum
条件 GMV sumIf() AggregateFunction(sum, T) sumIfState sumIfMerge
最新状态 argMax() 通常不直接作为状态列 argMax / Replacing argMax
Bitmap UV bitmap... Bitmap 状态 bitmapBuild/相关状态 bitmapCardinality/相关 Merge

第23章 物化汇总表常见错误

23.1 错误:把最终 AVG 直接存下来

hour_1 avg = 10
hour_2 avg = 20

(10 + 20) / 2 = 15

这不一定是真正的整体平均值。

正确:

avgState
    ↓
avgMerge

23.2 错误:把 UV 数字继续 uniq

错误:

uniq(uv)

正确:

uniqMerge(uv)

23.3 错误:MV 创建后历史数据自动进入汇总表

Incremental MV 主要对创建之后新插入的数据块执行。

历史数据需要回填:

INSERT INTO summary_table
SELECT ...
FROM raw_table
WHERE ...
GROUP BY ...;

回填之前要考虑:

  • 是否会与 MV 重复;
  • 是否需要暂停写入;
  • 是否需要临时目标表;
  • 是否需要分区回填。

23.4 错误:源表 DELETE 后汇总表自动同步

Incremental MV 不会因为源表后续 mutation 自动把历史聚合结果反向修正。

这意味着:

raw DELETE
   ≠
summary DELETE

生产系统必须明确设计修正机制。

23.5 错误:无限堆 MV

raw
 ↓
mv1
 ↓
mv2
 ↓
mv3
 ↓
mv4

容易造成:

  • 写入放大;
  • CPU 增加;
  • 依赖复杂;
  • 回填困难;
  • 数据一致性难排查。

第24章 汇总表查询模板库

24.1 最近 7 天 PV/UV/GMV

SELECT
    day,
    sum(pv) AS pv,
    uniqMerge(uv) AS uv,
    sumMerge(revenue) AS revenue
FROM dws_daily
WHERE day >= today() - 7
GROUP BY day
ORDER BY day;

24.2 按商品排行

SELECT
    product_id,
    sum(pv) AS pv,
    uniqMerge(uv) AS uv,
    sumMerge(revenue) AS revenue
FROM dws_daily
WHERE day >= today() - 30
GROUP BY product_id
ORDER BY revenue DESC
LIMIT 100;

24.3 按小时 P95

SELECT
    hour,
    quantileTDigestMerge(0.95)(p95_latency) AS p95
FROM dws_hourly
WHERE hour >= now() - INTERVAL 24 HOUR
GROUP BY hour
ORDER BY hour;

24.4 多维度统计

SELECT
    day,
    province,
    product_category,
    sumMerge(revenue) AS revenue,
    uniqMerge(uv) AS uv
FROM dws_daily
WHERE day >= today() - 30
GROUP BY
    day,
    province,
    product_category
ORDER BY
    day,
    revenue DESC;

24.5 最新状态

SELECT
    user_id,
    argMax(status, updated_at) AS latest_status,
    max(updated_at) AS latest_update
FROM user_status
GROUP BY user_id;

24.6 SummingMergeTree 汇总表

SELECT
    day,
    product_id,
    sum(order_count) AS orders,
    sum(amount) AS revenue
FROM sales_daily
WHERE day >= today() - 30
GROUP BY
    day,
    product_id;

第25章 最终学习框架:从“会用”到“真正掌握”

建议把 ClickHouse 学习分成六层:

L1 SQL
│
├─ SELECT
├─ GROUP BY
├─ JOIN
├─ Window
└─ Aggregate

L2 存储
│
├─ MergeTree
├─ Part
├─ Merge
├─ Partition
├─ Granule
└─ ORDER BY

L3 数据模型
│
├─ Replacing
├─ Summing
├─ Aggregating
├─ Coalescing
└─ CDC

L4 加速
│
├─ Materialized View
├─ Refreshable MV
├─ Projection
├─ Skip Index
├─ Dictionary
└─ Text Index

L5 集成
│
├─ Kafka
├─ S3
├─ MySQL/PostgreSQL
├─ Grafana
└─ CKibana / ClickStack

L6 生产
│
├─ Shard
├─ Replica
├─ Keeper
├─ Monitoring
├─ Backup
├─ Restore
├─ RPO/RTO
└─ Performance Tuning

最终原则

表引擎
   ↓
决定数据如何存、如何合并

View
   ↓
决定 SQL 如何复用

Materialized View
   ↓
决定计算什么时候发生

AggregateFunction
   ↓
决定聚合状态如何保存

State
   ↓
把聚合过程保存下来

Merge
   ↓
把多个聚合状态合并

汇总表
   ↓
把高频查询从“扫描明细”
变成“读取少量聚合数据”

Grafana / Kibana / CKibana / ClickStack
   ↓
负责展示与查询交互

最重要的一句话:

ClickHouse 性能优化不是“多建几个索引”,而是从查询模式 → ORDER BY → 数据类型 → 写入批次 → 引擎 → 聚合模型 → 物化视图 → 查询方式 → 集群资源形成完整闭环。

附录:速查、排查与设计原则

A. 常用 SQL 速查

系统信息

SELECT version();

SELECT *
FROM system.clusters;

SELECT *
FROM system.build_options;

SHOW TABLES;

SHOW CREATE TABLE events;

DESCRIBE TABLE events;

查询日志

SELECT *
FROM system.query_log
WHERE type = 'QueryFinish'
ORDER BY query_start_time DESC
LIMIT 20;

Part

SELECT *
FROM system.parts
WHERE active = 1
LIMIT 20;

Merge

SELECT *
FROM system.merges;

Mutation

SELECT *
FROM system.mutations
WHERE is_done = 0;

Replica

SELECT *
FROM system.replicas;

Kafka

SELECT *
FROM system.kafka_consumers;

手工控制

SYSTEM SYNC REPLICA events;

SYSTEM FLUSH LOGS;

KILL QUERY WHERE query_id = 'xxx';

B. 生产排查模板

查询慢

SQL
 ↓
query_id
 ↓
system.query_log
 ↓
read_rows/read_bytes
 ↓
EXPLAIN
 ↓
Primary Key
 ↓
Partition
 ↓
Projection/Skip Index
 ↓
JOIN/GROUP BY
 ↓
资源瓶颈

写入异常

INSERT QPS
 ↓
Part 数
 ↓
Merge
 ↓
磁盘 I/O
 ↓
Kafka lag
 ↓
分区数量
 ↓
单批数据大小

副本异常

system.replicas
 ↓
system.replication_queue
 ↓
Keeper
 ↓
网络
 ↓
磁盘
 ↓
重新同步

C. ClickHouse 设计原则总结

原则 1

先设计查询,再设计表。

原则 2

ORDER BY 是最重要的性能设计之一。

原则 3

Partition 主要解决数据管理和裁剪,不是万能性能索引。

原则 4

批量写入优先于大量小 INSERT。

原则 5

不要迷信 Skip Index。

原则 6

不要把 Replica 当 Backup。

原则 7

不要把 ClickHouse 当 OLTP。

原则 8

所有性能结论都应该用真实数据验证。

原则 9

新项目优先学习当前 26.x 能力,不要只学习 23.x 旧写法。

原则 10

生产环境必须同时考虑数据正确性、性能、成本、可运维性和灾备。


D. 原文主要修订项

本版重点修订以下问题:

原文问题 修订结果
将 23.x LTS 作为文档基线 改为 26.x 能力基线
“8192 行就是向量化批次” 修正为 granule/index granularity 概念
“高基数一定放排序键前” 修正为基于查询模式和数据局部性设计
“按天分区就是错误” 改为按数据量、生命周期和查询评估
“ClickHouse JOIN 能力有限” 更新为现代 JOIN 能力与成本评估
“更新/删除基本只能 mutation” 增加现代轻量 UPDATE/DELETE
Object('json') 作为主要 JSON 示例 改为现代 JSON 类型说明
allow_experimental_object_type 不再作为主流配置
固定旧版 apt-key 安装 删除旧式安装方式
固定 23.8.2.7 安装命令 改为版本中立安装
ckibana 作为主推荐 改为 Grafana/ClickStack 主线,ckibana 仅兼容说明
Kafka 参数与状态监控写法过于绝对 增加版本/环境校验说明
MySQL 表引擎 + MV 被描述为 CDC 修正为真正 CDC/ClickPipes/Kafka 等方案
OPTIMIZE FINAL 作为常规解决办法 改为谨慎使用
固定 Parts=10000 为危险线 改为综合指标判断
固定硬件配置 改为压测驱动
只讲副本同步,缺少 RPO/RTO 增加灾备体系
只讲传统物化视图 增加 Refreshable Materialized View
旧全文索引为主 增加现代 Text Index
生产权限示例偏 XML 增加 SQL RBAC 思路

E. 学习顺序

如果目标是从“会 SQL”提升到“能做生产 ClickHouse”,建议:

SQL
 ↓
MergeTree
 ↓
Part / Merge / Granule
 ↓
Partition
 ↓
ORDER BY / Primary Key
 ↓
数据类型
 ↓
EXPLAIN
 ↓
Materialized View
 ↓
Replacing/AggregatingMergeTree
 ↓
Kafka / CDC
 ↓
JOIN
 ↓
Projection / Skip Index / Text Index
 ↓
监控
 ↓
Keeper / Replica / Shard
 ↓
Backup / Restore
 ↓
性能压测
 ↓
生产架构

F. 结论

ClickHouse 的学习重点不是“把所有 SQL 语法背下来”,而是形成以下完整能力:

业务场景
   ↓
查询模式
   ↓
数据模型
   ↓
ORDER BY / Partition
   ↓
写入链路
   ↓
查询链路
   ↓
性能诊断
   ↓
集群与副本
   ↓
监控
   ↓
备份恢复
   ↓
生产治理

真正达到生产级水平的标准,不是“能把 ClickHouse 跑起来”,而是能够回答:

  1. 为什么这个表这样设计?
  2. 为什么 ORDER BY 是这些字段?
  3. 为什么按月而不是按天分区?
  4. 为什么使用 ReplacingMergeTree?
  5. 为什么不用 FINAL?
  6. 为什么这个查询扫描这么多数据?
  7. 为什么出现 Too Many Parts?
  8. 为什么副本延迟?
  9. 为什么 Kafka 消费变慢?
  10. 如果误删数据,多久能恢复?
  11. 如果一个 Shard 故障,业务是否还能继续?
  12. 如果数据增长 10 倍,架构还能不能撑住?

能够独立回答并通过压测验证这些问题,才是真正意义上的 ClickHouse 全栈能力。

posted @ 2026-08-08 13:33  小郑[努力版]  阅读(10)  评论(0)    收藏  举报