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 编码与压缩
常见组合包括:
LowCardinalityDeltaDoubleDeltaGorillaT64LZ4ZSTD
压缩比没有固定的“10 倍、20 倍”保证,实际结果取决于数据分布、排序键和字段类型。
1.3.6 JIT (即时编译)
ClickHouse 具备表达式 JIT 编译能力,但不要把 JIT 当作所有查询都自动执行的核心机制。
性能分析时仍应首先关注:
- 是否读取过多数据;
- 主键是否有效;
- 是否存在过多 Part;
- JOIN/聚合是否产生内存瓶颈;
- 是否需要预聚合或 Projection;
- 是否存在不必要的函数计算。
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 中容易误导。
实际应该:
- 从真实 WHERE/范围查询模式出发;
- 考虑等值过滤、范围过滤和排序;
- 考虑列之间的基数与数据局部性;
- 通常让有选择性的、稳定的过滤维度成为排序前缀;
- 对低基数列放前也可能非常有效,尤其在数据局部性和压缩方面;
- 最终通过
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 优化之后再考虑。
常见类型:
minmaxsetbloom_filtertokenbf_v1ngrambf_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_rows、read_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 官方也推荐理解 FINAL 与 argMax 的权衡。
第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 跑起来”,而是能够回答:
- 为什么这个表这样设计?
- 为什么 ORDER BY 是这些字段?
- 为什么按月而不是按天分区?
- 为什么使用 ReplacingMergeTree?
- 为什么不用 FINAL?
- 为什么这个查询扫描这么多数据?
- 为什么出现 Too Many Parts?
- 为什么副本延迟?
- 为什么 Kafka 消费变慢?
- 如果误删数据,多久能恢复?
- 如果一个 Shard 故障,业务是否还能继续?
- 如果数据增长 10 倍,架构还能不能撑住?
能够独立回答并通过压测验证这些问题,才是真正意义上的 ClickHouse 全栈能力。

浙公网安备 33010602011771号