数据库06:ClickHouse
从列式存储、MergeTree 引擎、分布式表到 OLAP 查询优化,理解 ClickHouse 在大数据分析场景下的极致性能设计。
ClickHouse 是俄罗斯 Yandex 公司开源的列式存储数据库,专为 OLAP(在线分析处理)场景设计。它的查询速度是传统关系型数据库的几十倍甚至上百倍。
这篇文章围绕四个核心点:列式存储、MergeTree 引擎、分布式表、OLAP 查询优化,理解 ClickHouse 的极致性能设计。
列式存储 vs 行式存储
传统数据库(MySQL、PostgreSQL)使用行式存储,而 ClickHouse 使用列式存储,这是它性能优势的根源。
存储方式对比
| 维度 | 行式存储 | 列式存储 |
|---|---|---|
| 数据组织 | 按行存储,一行数据连续存放 | 按列存储,一列数据连续存放 |
| 读取方式 | 需要读取整行,即使只需要部分列 | 只读取需要的列,减少 IO |
| 压缩率 | 低,数据类型多样 | 高,同类数据压缩效果好 |
| 适用场景 | OLTP(在线事务处理) | OLAP(在线分析处理) |
列式存储的优势
- 减少 IO:分析查询通常只需要部分列,列式存储只读取需要的列
- 更高压缩率:同类数据更容易压缩,压缩率可达 10-20 倍
- 向量化执行:现代 CPU 支持 SIMD 指令,可以批量处理列数据
- 高效聚合:列数据连续存储,聚合操作效率高
MergeTree:ClickHouse 的核心引擎
MergeTree 是 ClickHouse 默认且最强大的存储引擎,它的设计思路是”写入时追加,后台合并”。
MergeTree 核心特性
- 主键索引:按主键排序,加速点查询和范围查询
- 分区:按分区键(如日期)组织数据,支持分区裁剪
- 数据合并:后台线程自动合并小数据块,优化查询性能
- 副本:支持副本机制,保证数据高可用
- TTL:支持数据自动过期
MergeTree 家族
| 引擎 | 特点 | 适用场景 |
|---|---|---|
| MergeTree | 基础版本 | 大多数场景 |
| ReplacingMergeTree | 去重,保留最新版本 | 需要去重的场景 |
| SummingMergeTree | 预聚合,自动求和 | 求和场景 |
| AggregatingMergeTree | 预聚合,自定义聚合 | 复杂聚合场景 |
| CollapsingMergeTree | 折叠,处理增量数据 | 日志分析 |
| VersionedCollapsingMergeTree | 带版本的折叠 | 需要保留历史版本 |
创建表示例
CREATE TABLE orders (
id UInt64,
user_id UInt64,
amount Float64,
status String,
created_at DateTime
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(created_at)
ORDER BY (user_id, created_at)
TTL created_at + INTERVAL 90 DAY;
分布式表:水平扩展
ClickHouse 通过分布式表实现水平扩展,将数据分散到多个节点。
分布式表架构
Distributed Table(分布式表)
├── Shard 1(分片1)
│ └── Local Table(本地表)
├── Shard 2(分片2)
│ └── Local Table(本地表)
└── Shard 3(分片3)
└── Local Table(本地表)
分片策略
| 策略 | 方式 | 优点 | 缺点 |
|---|---|---|---|
| 哈希分片 | 按字段哈希值分配 | 数据均匀分布 | 不支持范围查询优化 |
| 范围分片 | 按字段范围分配 | 范围查询高效 | 需要手动维护分片规则 |
| 随机分片 | 随机分配 | 简单 | 查询需要扫描所有分片 |
创建分布式表
-- 创建本地表
CREATE TABLE orders_local AS orders ENGINE = MergeTree()
PARTITION BY toYYYYMM(created_at)
ORDER BY (user_id, created_at);
-- 创建分布式表
CREATE TABLE orders_all AS orders ENGINE = Distributed(
cluster_name,
database_name,
orders_local,
rand() -- 分片键
);
查询路由
- 本地查询:查询本地表,只访问当前节点
- 分布式查询:查询分布式表,自动路由到所有分片
- 数据写入:写入分布式表,自动分发到所有分片
OLAP 查询优化
ClickHouse 的查询优化和传统数据库有很大不同,需要针对 OLAP 场景进行调优。
分区裁剪
-- 好:只扫描 2024 年 1 月的分区
SELECT COUNT(*) FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2024-02-01';
-- 不好:扫描所有分区
SELECT COUNT(*) FROM orders WHERE toMonth(created_at) = 1;
主键索引
-- 好:使用主键索引
SELECT * FROM orders WHERE user_id = 123 AND created_at >= '2024-01-01';
-- 不好:无法使用主键索引
SELECT * FROM orders WHERE amount > 100;
预聚合
使用 SummingMergeTree 或 AggregatingMergeTree 进行预聚合:
CREATE TABLE orders_agg (
user_id UInt64,
month UInt64,
total_amount AggregateFunction(sum, Float64),
order_count AggregateFunction(count, UInt64)
) ENGINE = AggregatingMergeTree()
PARTITION BY month
ORDER BY user_id;
-- 写入聚合数据
INSERT INTO orders_agg SELECT
user_id,
toYYYYMM(created_at) as month,
sumState(amount),
countState(*)
FROM orders
GROUP BY user_id, month;
-- 查询聚合数据
SELECT
user_id,
sumMerge(total_amount),
countMerge(order_count)
FROM orders_agg
GROUP BY user_id;
数据复制与高可用
ClickHouse 支持两种复制方式:
| 方式 | 特点 | 适用场景 |
|---|---|---|
| ReplicatedMergeTree | 基于 ZooKeeper 的副本机制 | 需要高可用的场景 |
| 外部复制 | 使用外部工具(如 Kafka)同步数据 | 数据管道场景 |
项目判断清单
- 需要实时数据分析 → ClickHouse
- 需要处理海量数据(TB/PB 级)→ ClickHouse
- 需要复杂聚合查询 → ClickHouse
- 需要高并发写入(日志、埋点)→ ClickHouse
- 需要事务支持 → 不适合,考虑 PostgreSQL 或 TiDB
- 需要实时查询最新数据 → 考虑 Kafka + ClickHouse
- 需要多租户隔离 → 使用分布式表和用户权限