数据库

ClickHouse 原理、应用场景与 MergeTree 引擎详解

By karp 5 Views 61 MIN READ 0 Comments

前言

ClickHouse 是一款面向大数据分析场景的列式数据库,尤其适合日志分析、行为统计、实时指标计算和多维度报表等 OLAP 场景。

本文将从以下几个方面介绍 ClickHouse:

  • ClickHouse 是什么
  • ClickHouse 的底层数据结构
  • ClickHouse 为什么查询速度快
  • ClickHouse 的适用场景
  • ClickHouse 开发规范与注意事项
  • 常见 MergeTree 系列存储引擎
  • ClickHouse、MySQL 与 TiDB 的区别

一、什么是 ClickHouse

ClickHouse 是由 Yandex 开发并开源的一款高性能、分布式、面向分析场景的列式数据库。

它能够对大规模数据进行快速搜索、聚合、过滤、排序和统计,主要应用于 OLAP,即联机分析处理场景。

ClickHouse 的核心特点

1. 列式存储

ClickHouse 按列存储数据,而不是像 MySQL InnoDB 一样按行存储。

查询时只读取需要使用的列,可以有效减少磁盘 I/O 和内存消耗。

2. 聚合性能高

ClickHouse 非常擅长执行以下统计和聚合操作:

COUNT()
MAX()
MIN()
SUM()
AVG()
GROUP BY
ORDER BY

它可以在海量数据中快速完成分组、汇总和排序。

3. 适合大规模数据分析

ClickHouse 常用于处理:

  • 大规模日志
  • 时间序列数据
  • 用户行为数据
  • 监控指标
  • IoT 数据
  • 广告统计
  • 交易流水
  • BI 报表

4. SQL 学习成本较低

ClickHouse 使用类似传统关系型数据库的 SQL 语法。

相比 Hadoop、Spark、InfluxDB 等生态,开发人员通常可以更快上手。


二、ClickHouse 底层数据结构

2.1 核心存储引擎:MergeTree

MergeTree 是 ClickHouse 最核心、最常用的存储引擎。

它采用以下设计:

  • 顺序批量写入
  • 数据按列存储
  • 数据按排序键排列
  • 后台异步合并
  • 按分区管理数据
  • 使用稀疏索引跳过无关数据

MergeTree 在部分设计思想上与 LSM Tree 类似,例如批量写入和后台合并,但它并不是传统意义上的 LSM Tree。

在实际开发中,大多数 ClickHouse 表都会直接或间接使用 MergeTree 系列引擎。


2.2 数据结构分层

ClickHouse 中 MergeTree 表的数据结构,可以抽象为:

Table
└── Partition
    └── Part
        ├── Column Files
        │   ├── column1.bin
        │   ├── column2.bin
        │   └── ...
        ├── Mark Files
        │   ├── column1.mrk3
        │   ├── column2.mrk3
        │   └── ...
        └── Primary Index

各层含义如下。

Table

ClickHouse 中的一张逻辑表。

Partition

分区是数据管理单位,通常按照日期、月份或业务维度进行划分。

例如:

PARTITION BY toYYYYMM(create_time)

表示按月份进行分区。

Part

每次批量写入 ClickHouse 后,通常会在对应分区中生成一个新的数据 Part。

每个 Part 都是一组独立的数据文件和索引文件。

Column Files

每一列的数据会分别存储。

常见文件包括:

column1.bin
column1.mrk3

其中:

  • .bin 文件存储经过压缩后的列数据
  • .mrk3 文件存储数据块对应的标记信息,用于快速定位数据位置

2.3 数据写入流程

ClickHouse 的典型写入流程如下:

客户端批量 INSERT
        ↓
数据排序并生成新的 Part
        ↓
Part 写入磁盘
        ↓
后台 Merge 线程选择多个小 Part
        ↓
合并成更大的 Part
        ↓
旧 Part 被删除

具体来说:

  1. 客户端批量提交数据。
  2. ClickHouse 根据表的排序键对数据进行排序。
  3. 数据被写入一个新的 Part。
  4. 每个 Part 包含独立的列文件、标记文件和索引。
  5. 后台任务会异步合并多个小 Part。
  6. 最终生成更大、更紧凑的数据 Part。

因此,ClickHouse 更适合批量写入,不适合高频率地逐行写入。


2.4 索引机制

ClickHouse 的索引设计与 MySQL 的 B+Tree 索引存在明显区别。

ClickHouse 的主要目标不是快速定位某一行数据,而是快速跳过大量不相关的数据块。

1. 主键稀疏索引

MergeTree 表中的主键索引通常是稀疏索引。

它不会为每一行数据建立索引,而是按照一定的数据粒度保存索引标记。

默认索引粒度通常为:

8192 行

这意味着 ClickHouse 会以 Granule 为单位读取和过滤数据。

需要注意,实际读取粒度还可能受到 index_granularity_bytes 等配置影响,并不一定永远严格对应 8192 行。

2. Mark 文件

.mrk3 文件记录列数据中每个 Granule 对应的物理位置。

查询时,ClickHouse 可以根据索引快速定位到相关的数据范围,而不需要从头扫描整个列文件。

3. Min-Max 索引

ClickHouse 会为分区或数据块维护最小值和最大值信息。

例如某个数据块中的时间范围是:

2026-08-01 00:00:00
~
2026-08-01 01:00:00

当查询条件是:

WHERE create_time >= '2026-08-02 00:00:00'

该数据块就可以直接被跳过。

4. Data Skipping Index

ClickHouse 支持可选的跳数索引,例如:

  • minmax
  • set
  • bloom_filter
  • tokenbf_v1
  • ngrambf_v1

例如:

INDEX idx_user_id user_id TYPE bloom_filter GRANULARITY 4

跳数索引的作用是帮助 ClickHouse 判断某个数据块是否可能包含目标数据。

它并不是传统数据库中的精确二级索引。

小结

ClickHouse 很适合根据排序键、分区键以及跳数索引字段进行范围筛选。

但它不像 MySQL 一样擅长通过 B+Tree 索引进行单行点查。


2.5 后台合并机制

MergeTree 会在后台自动对 Part 进行合并。

合并过程中可能完成:

  • 多个小 Part 合并
  • 数据重新排序
  • 数据压缩
  • TTL 数据清理
  • ReplacingMergeTree 去重
  • SummingMergeTree 求和
  • AggregatingMergeTree 聚合状态合并
  • CollapsingMergeTree 正负记录折叠

需要注意:

ClickHouse 的后台 Merge 并不意味着完全“无锁”,更准确地说,它通过不可变 Part、后台异步合并和原子切换等机制,尽量降低对前台查询和写入的影响。


三、ClickHouse 为什么快

3.1 列式存储

ClickHouse 每一列单独存储。

例如下面的查询:

SELECT
    user_id,
    SUM(amount)
FROM trade_records
WHERE create_time >= '2026-08-01'
GROUP BY user_id;

查询只需要读取:

  • user_id
  • amount
  • create_time

其他列不会被读取。

这样可以:

  • 减少磁盘读取
  • 减少网络传输
  • 减少内存使用
  • 提高 CPU 缓存命中率

这非常适合从宽表中读取少量字段进行分析。


3.2 高压缩率

同一列中的数据类型通常相同,并且数据分布相似。

例如:

1001
1002
1003
1004

或者:

2026-08-01 10:00:01
2026-08-01 10:00:02
2026-08-01 10:00:03

这类数据更容易被压缩。

ClickHouse 常用的压缩算法包括:

  • LZ4
  • ZSTD

列式存储配合压缩,可以显著减少磁盘空间和磁盘 I/O。


3.3 SIMD 与向量化执行

ClickHouse 会以数据块为单位进行批量处理,而不是一行一行执行。

例如在计算:

SELECT SUM(amount) FROM trade_records;

ClickHouse 会一次读取一批数据,并使用向量化方式进行计算。

这能够:

  • 减少函数调用开销
  • 减少循环判断
  • 提高 CPU 指令执行效率
  • 更好地利用 SIMD 指令集

相比逐行处理,批量计算的吞吐量更高。


3.4 多级数据跳过机制

ClickHouse 查询时会尝试跳过无关数据。

常见的数据过滤层级包括:

分区裁剪
    ↓
主键稀疏索引
    ↓
Data Skipping Index
    ↓
读取相关 Granule
    ↓
执行 WHERE 条件

例如:

SELECT *
FROM trade_records
WHERE create_time >= '2026-08-01'
  AND user_id = 10001;

ClickHouse 可能依次进行:

  1. 通过分区键排除无关月份。
  2. 通过排序键范围排除无关 Granule。
  3. 通过跳数索引进一步过滤。
  4. 只读取可能包含目标数据的数据块。

这也是 ClickHouse 查询速度快的重要原因。


3.5 MergeTree 的顺序写入机制

MergeTree 写入数据时,一般不会直接修改历史 Part。

新的数据会写入新的 Part,旧数据保持不变。

这种不可变数据文件设计具有以下优点:

  • 写入过程简单
  • 适合批量写入
  • 减少随机磁盘写
  • 前台查询与后台合并可以并行
  • 数据文件更容易压缩

后台任务再逐步将多个小 Part 合并成大 Part。


3.6 并行查询能力

ClickHouse 可以充分利用服务器的多核 CPU。

一次查询通常可以并行执行:

  • 多个 Part 并行扫描
  • 多个列并行读取
  • 多线程聚合
  • 多线程排序
  • 多线程解压缩
  • 分布式节点并行查询

因此,ClickHouse 更关注整体吞吐量,而不是单行查询延迟。


3.7 查询优化机制

ClickHouse 支持多种查询优化:

  • 分区裁剪
  • 主键条件下推
  • PREWHERE
  • 投影裁剪
  • 表达式计算优化
  • 聚合并行化
  • JOIN 算法选择
  • 数据跳过索引
  • Projection
  • 物化视图

ClickHouse 的优化重点主要面向大规模扫描、过滤和聚合。


四、ClickHouse 的适用场景

4.1 适合的场景

1. 大数据 OLAP 分析

例如:

  • 订单统计
  • 交易金额统计
  • 用户行为分析
  • 平均值、最大值、最小值计算
  • 多维度分组
  • 实时报表

2. 时间序列分析

例如:

  • 服务器监控
  • 应用指标
  • IoT 数据
  • 系统埋点
  • 业务指标
  • 交易行情

3. 日志分析

例如:

  • Nginx 日志
  • 网关访问日志
  • 应用错误日志
  • 审计日志
  • 链路追踪数据
  • 安全分析日志

4. 用户行为分析

例如:

  • 页面访问量
  • 用户点击
  • 用户留存
  • 转化漏斗
  • 用户路径
  • 广告曝光和点击

5. BI 报表

例如:

  • 数据看板
  • 运营报表
  • 财务统计
  • 交易报表
  • 风控报表
  • 实时排行榜

4.2 不适合的场景

1. 高频 OLTP 事务

ClickHouse 不适合作为订单主库、账户主库或支付核心账务数据库。

它不擅长:

  • 高频单行写入
  • 高频单行更新
  • 高频单行删除
  • 强事务操作
  • 复杂行级锁
  • 极低延迟点查

2. 强一致性事务

ClickHouse 支持部分事务相关能力,但并不等同于 MySQL InnoDB 的完整 OLTP 事务模型。

不建议依赖 ClickHouse 实现:

  • 多表强一致事务
  • 资金扣减
  • 库存扣减
  • 账户余额更新
  • 订单状态机
  • 核心账务一致性

3. 高频点查

例如:

SELECT *
FROM users
WHERE user_id = 10001;

如果业务需要每秒大量执行这种单条记录查询,MySQL、PostgreSQL、Redis 或其他 KV 数据库通常更适合。

4. 高频 UPDATE 和 DELETE

ClickHouse 支持 Mutation,例如:

ALTER TABLE users
UPDATE status = 2
WHERE user_id = 10001;

以及:

ALTER TABLE users
DELETE
WHERE user_id = 10001;

但 Mutation 通常需要重写数据 Part,成本较高。

因此应该尽量避免频繁使用。

5. 复杂多表事务 JOIN

ClickHouse 支持 JOIN,并且近年来 JOIN 能力不断增强。

但对于大量复杂、多层、频繁变化的关系型 JOIN 业务,传统关系型数据库通常更合适。


五、ClickHouse 开发注意事项

5.1 合理设计分区键

分区键主要影响:

  • 数据生命周期管理
  • 分区裁剪
  • 数据删除
  • 后台合并范围
  • 数据维护成本

常见设计:

PARTITION BY toYYYYMM(create_time)

不要将用户 ID、订单 ID 等高基数字段直接作为分区键,否则可能产生大量小分区。

通常建议:

  • 按月分区
  • 按天分区
  • 按业务类型和日期组合分区

具体粒度需要根据数据规模和数据生命周期决定。


5.2 合理设计 ORDER BY

在 MergeTree 中,ORDER BY 是最重要的表结构设计之一。

它决定:

  • 数据在磁盘上的排序方式
  • 主键稀疏索引的组织方式
  • 查询能够跳过多少数据
  • 相同维度的数据是否相邻
  • 后台合并效率

例如:

ORDER BY (user_id, create_time)

适合经常根据用户和时间范围进行查询的场景。

设计原则:

  1. 优先放置最常用的过滤字段。
  2. 优先考虑等值过滤和范围过滤组合。
  3. 注意字段顺序。
  4. 不要盲目追求最高区分度。
  5. 需要结合实际查询条件设计。

例如:

ORDER BY (symbol, create_time, user_id)

通常比:

ORDER BY (order_id, create_time)

更适合按照交易对和时间范围统计的场景。


5.3 PRIMARY KEY 不等于唯一键

ClickHouse 中的主键不会自动保证唯一性。

例如:

PRIMARY KEY (user_id)

并不意味着 user_id 不能重复。

ClickHouse 的主键主要用于创建稀疏索引,帮助跳过无关数据。

此外,PRIMARY KEYORDER BY 也并不完全等价:

  • 如果不单独指定 PRIMARY KEY,通常会使用 ORDER BY 表达式作为主键。
  • 可以显式指定一个作为 ORDER BY 前缀的 PRIMARY KEY

例如:

ENGINE = MergeTree
PRIMARY KEY (user_id)
ORDER BY (user_id, create_time)

5.4 避免小批量 INSERT

ClickHouse 每次 INSERT 都可能产生新的 Part。

如果频繁执行:

INSERT INTO logs VALUES (...);

可能生成大量小 Part,导致:

  • 后台 Merge 压力增大
  • 文件数量增加
  • 磁盘 I/O 增加
  • 查询性能下降
  • 出现 Too many parts 错误

建议使用批量写入。

例如:

每批 1,000 条以上

实际生产环境中,通常会使用更大的批次,例如:

10,000 ~ 100,000 条

具体大小需要根据单行数据大小、写入延迟要求和服务器资源综合判断。


5.5 避免 SELECT *

不推荐:

SELECT *
FROM trade_records;

推荐:

SELECT
    user_id,
    symbol,
    amount,
    create_time
FROM trade_records;

列式数据库只读取需要的列。

使用 SELECT * 会导致额外的磁盘读取、解压缩和网络传输。


5.6 尽量避免频繁 UPDATE 和 DELETE

对于状态变更,可以考虑采用追加写入模式。

例如,不直接修改旧记录,而是写入新版本:

order_id = 1001, status = 1, version = 1
order_id = 1001, status = 2, version = 2
order_id = 1001, status = 3, version = 3

然后使用:

ReplacingMergeTree(version)

保留较新的版本。


5.7 减少运行时复杂 JOIN

可以考虑:

  • 宽表
  • 字典表
  • 物化视图
  • Projection
  • 预聚合表
  • ETL 预处理
  • 在写入阶段完成维度补充

ClickHouse 并不是完全不能 JOIN,而是应该避免让高频分析查询依赖复杂的多层 JOIN。


5.8 ClickHouse 应主要承担分析职责

推荐架构:

MySQL / PostgreSQL
        ↓
Kafka / Pulsar
        ↓
Flink / ETL
        ↓
ClickHouse
        ↓
BI / 报表 / 风控分析 / 数据查询

在该架构中:

  • MySQL 或 PostgreSQL 承担 OLTP 业务
  • Kafka 或 Pulsar 承担数据传输
  • Flink 或 ETL 任务负责清洗和转换
  • ClickHouse 承担 OLAP 查询

六、MergeTree 系列存储引擎

6.1 MergeTree

说明

MergeTree 是最基础、最通用的 MergeTree 系列引擎。

它支持:

  • 分区
  • 排序键
  • 主键稀疏索引
  • TTL
  • 数据压缩
  • 跳数索引
  • 后台合并

适用场景

适用于大多数明细数据分析:

  • 日志采集
  • 用户行为
  • 交易流水
  • 监控指标
  • 埋点数据
  • IoT 数据

示例

CREATE TABLE access_logs
(
    event_time DateTime,
    user_id UInt64,
    path String,
    status_code UInt16
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, user_id);

6.2 ReplacingMergeTree

说明

ReplacingMergeTree 用于在后台合并阶段删除排序键相同的重复记录。

可以指定版本字段:

ReplacingMergeTree(version)

当存在多条相同排序键的数据时,通常会保留版本较大的记录。

注意事项

ReplacingMergeTree 的去重具有以下特点:

  • 去重发生在后台 Merge 阶段
  • 不能保证写入后立即完成去重
  • 不同分区之间不会相互合并
  • 查询时可能暂时看到重复数据
  • 使用 FINAL 可以在查询阶段强制合并逻辑,但成本较高

因此,它不是严格意义上的唯一键约束。

适用场景

  • 数据可能重复写入
  • 用户画像
  • 资产快照
  • 订单状态快照
  • CDC 数据同步
  • 保留最新版本的数据

示例

CREATE TABLE user_profile
(
    user_id UInt64,
    nickname String,
    level UInt32,
    version UInt64,
    update_time DateTime
)
ENGINE = ReplacingMergeTree(version)
PARTITION BY toYYYYMM(update_time)
ORDER BY user_id;

查询最新结果时:

SELECT *
FROM user_profile FINAL;

在大表上应谨慎使用 FINAL


6.3 SummingMergeTree

说明

SummingMergeTree 会在后台合并阶段,对排序键相同记录中的数值字段进行求和。

它类似预先执行:

GROUP BY ... SUM(...)

但求和过程主要发生在 Part 合并阶段。

适用场景

适用于明细写入量大,但最终只关心累计值的场景:

  • 页面访问量
  • 点击量
  • 广告收入
  • 交易金额
  • 订单数量
  • 流量统计

示例

CREATE TABLE page_view_stats
(
    page_id UInt32,
    dt Date,
    sum_views UInt64
)
ENGINE = SummingMergeTree(sum_views)
PARTITION BY toYYYYMM(dt)
ORDER BY (dt, page_id);

写入数据:

INSERT INTO page_view_stats VALUES
(1001, '2026-08-01', 10),
(1001, '2026-08-01', 20);

后台合并后,相同排序键的数据可能被合并为:

page_id = 1001
dt = 2026-08-01
sum_views = 30

注意事项

后台合并是异步的,因此查询时仍然应该使用 SUM()

SELECT
    page_id,
    SUM(sum_views) AS total_views
FROM page_view_stats
WHERE dt = '2026-08-01'
GROUP BY page_id;

不要假设磁盘上已经只剩一条记录。


6.4 CollapsingMergeTree

说明

CollapsingMergeTree 适用于“插入一条记录,再插入一条反向记录进行撤销”的场景。

它通过一个 Sign 字段标记数据状态:

  • 1:正向记录
  • -1:撤销记录

当排序键相同的正负记录在后台合并时,可能被折叠消除。

适用场景

  • 订单状态撤销
  • 事务日志补偿
  • 状态变更记录
  • 数据修正
  • 事件抵消

示例

CREATE TABLE user_orders
(
    order_id UInt64,
    user_id UInt64,
    amount Decimal(20, 8),
    event_time DateTime,
    Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
PARTITION BY toYYYYMM(event_time)
ORDER BY order_id;

插入订单:

INSERT INTO user_orders VALUES
(10001, 20001, 100.00, now(), 1);

撤销订单:

INSERT INTO user_orders VALUES
(10001, 20001, 100.00, now(), -1);

注意事项

CollapsingMergeTree 对数据写入顺序、排序键和正负记录的配对要求较高。

设计不当可能出现:

  • 状态不一致
  • 正负记录无法抵消
  • 查询结果重复
  • 分布式写入顺序问题

因此在使用前需要充分理解其折叠规则。


6.5 VersionedCollapsingMergeTree

VersionedCollapsingMergeTree 是 CollapsingMergeTree 的扩展版本。

它增加了版本字段,可以更好地处理乱序写入。

示例:

CREATE TABLE order_status
(
    order_id UInt64,
    status UInt8,
    version UInt64,
    Sign Int8
)
ENGINE = VersionedCollapsingMergeTree(Sign, version)
ORDER BY order_id;

它适合数据可能乱序到达的状态更新场景。


6.6 AggregatingMergeTree

说明

AggregatingMergeTree 用于存储聚合函数的中间状态。

它不是直接存储最终的求和结果,而是存储:

sumState()
avgState()
uniqState()
quantileState()

等聚合状态。

在后台合并时,ClickHouse 会合并这些中间状态。

适用场景

  • 每小时订单聚合
  • 每日销售额聚合
  • 多维度报表
  • 用户行为统计
  • 去重用户数
  • 分位数统计
  • 预聚合计算

这样可以避免每次查询都扫描和聚合原始明细表。

创建聚合表

字段类型必须使用 AggregateFunctionSimpleAggregateFunction

CREATE TABLE daily_sales_agg
(
    shop_id UInt64,
    dt Date,
    sales_state AggregateFunction(sum, Decimal(20, 8))
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(dt)
ORDER BY (dt, shop_id);

写入聚合状态

插入时需要使用 sumState()

INSERT INTO daily_sales_agg
SELECT
    shop_id,
    dt,
    sumState(sales_amount) AS sales_state
FROM raw_sales_data
GROUP BY
    shop_id,
    dt;

查询最终结果

查询时应使用对应的 sumMerge()

SELECT
    shop_id,
    sumMerge(sales_state) AS total_sales
FROM daily_sales_agg
WHERE dt = '2026-08-01'
GROUP BY shop_id;

需要注意:

merge(sales_state)

不是标准的聚合函数调用方式。

不同状态需要使用对应的 Merge 函数:

sumMerge()
avgMerge()
uniqMerge()
quantileMerge()

七、ClickHouse 物化视图

ClickHouse 的物化视图通常用于在数据写入时自动转换或聚合数据。

例如原始订单表:

CREATE TABLE raw_orders
(
    order_id UInt64,
    shop_id UInt64,
    amount Decimal(20, 8),
    create_time DateTime
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(create_time)
ORDER BY (create_time, shop_id);

创建聚合目标表:

CREATE TABLE daily_order_stats
(
    dt Date,
    shop_id UInt64,
    order_count UInt64,
    total_amount Decimal(20, 8)
)
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(dt)
ORDER BY (dt, shop_id);

创建物化视图:

CREATE MATERIALIZED VIEW daily_order_stats_mv
TO daily_order_stats
AS
SELECT
    toDate(create_time) AS dt,
    shop_id,
    count() AS order_count,
    sum(amount) AS total_amount
FROM raw_orders
GROUP BY
    dt,
    shop_id;

之后向 raw_orders 写入数据时,物化视图会自动将聚合结果写入 daily_order_stats


八、ClickHouse、MySQL 与 TiDB 对比

系统主要定位存储方式主要索引或组织方式适合场景主要短板
ClickHouseOLAP列式存储、Partition、Part排序键、稀疏主键索引、Min-Max、跳数索引日志分析、分组统计、实时报表、大规模聚合单行更新成本高,不适合核心 OLTP 和高频点查
MySQLOLTPInnoDB 行式存储B+Tree 聚簇索引、二级索引高频点查、事务、订单、账户、业务系统大规模扫描和复杂聚合性能相对较弱
TiDB分布式 HTAPTiKV 行存 + TiFlash 列存TiKV 基于 RocksDB,TiFlash 使用列式副本分布式事务、水平扩展、同时承担部分 OLTP 与 OLAP架构和运维复杂,资源成本较高,存在分布式开销

九、ClickHouse 与 MySQL 的核心差异

MySQL 查询思路

MySQL 通常通过 B+Tree 索引快速定位具体记录:

索引查找
    ↓
定位主键
    ↓
回表
    ↓
读取一行或少量行

适合:

SELECT *
FROM orders
WHERE order_id = 10001;

ClickHouse 查询思路

ClickHouse 通常通过分区和稀疏索引排除大量数据块:

分区裁剪
    ↓
索引范围判断
    ↓
跳过无关 Granule
    ↓
批量读取相关列
    ↓
向量化计算

适合:

SELECT
    symbol,
    SUM(amount)
FROM orders
WHERE create_time >= '2026-08-01'
GROUP BY symbol;

可以简单理解为:

MySQL 擅长快速找到某几行数据,ClickHouse 擅长快速扫描和计算大量数据。

十、常见表结构示例

下面是一张交易流水 ClickHouse 表:

CREATE TABLE trade_records
(
    trade_id UInt64,
    order_id UInt64,
    user_id UInt64,
    symbol LowCardinality(String),
    side Enum8(
        'buy' = 1,
        'sell' = 2
    ),
    price Decimal(30, 10),
    quantity Decimal(30, 10),
    amount Decimal(30, 10),
    fee Decimal(30, 10),
    create_time DateTime64(3)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(create_time)
ORDER BY
(
    symbol,
    create_time,
    user_id,
    trade_id
)
TTL toDateTime(create_time) + INTERVAL 3 YEAR
SETTINGS index_granularity = 8192;

该表适合以下查询:

SELECT
    symbol,
    side,
    count() AS trade_count,
    sum(quantity) AS total_quantity,
    sum(amount) AS total_amount
FROM trade_records
WHERE create_time >= now() - INTERVAL 1 DAY
GROUP BY
    symbol,
    side
ORDER BY total_amount DESC;

十一、开发规范总结

表结构设计

  • 合理选择分区键。
  • 根据高频查询条件设计 ORDER BY
  • 不要把 PRIMARY KEY 当作唯一约束。
  • 避免创建过多分区。
  • 合理使用 LowCardinality、Enum、Decimal 等数据类型。
  • 时间字段尽量使用 Date、DateTime 或 DateTime64。
  • 金额字段不要使用 Float,优先使用 Decimal。

数据写入

  • 使用批量 INSERT。
  • 避免一条数据执行一次 INSERT。
  • 控制 Part 数量。
  • 大规模写入可以通过 Kafka、Pulsar、Flink 或 ClickHouse Kafka Engine 接入。
  • 数据去重应尽量在上游完成。

数据查询

  • 避免 SELECT *
  • 查询条件尽量包含分区键或排序键。
  • 避免无条件扫描全表。
  • 谨慎使用 FINAL
  • 谨慎执行大规模 JOIN。
  • 查询前先通过 EXPLAIN 分析执行计划。

数据更新

  • 尽量采用追加写入。
  • 高频状态变更可以使用版本字段。
  • 避免频繁 Mutation。
  • 删除历史数据优先使用 Partition 删除或 TTL。

系统定位

  • ClickHouse 主要负责分析,不负责核心业务事务。
  • MySQL、PostgreSQL 负责业务真值数据。
  • ClickHouse 负责统计、聚合、日志和报表。
  • 不要将 ClickHouse 当作 MySQL 的直接替代品。

十二、总结

ClickHouse 的高性能主要来自以下几个方面:

  1. 列式存储,只读取需要的字段。
  2. 高效压缩,减少磁盘 I/O。
  3. 稀疏索引和跳数索引,快速跳过无关数据。
  4. 向量化执行,批量利用 CPU。
  5. 多线程和分布式并行查询。
  6. MergeTree 顺序写入与后台异步合并。
  7. 针对聚合、过滤、排序等 OLAP 操作进行优化。

在实际系统中,ClickHouse 最合适的定位通常是:

业务数据库负责交易和事务
          +
ClickHouse 负责分析和统计

一句话概括:

ClickHouse 不是为了快速修改某一行数据,而是为了快速分析数十亿行数据。

本文由 karp 原创

采用 CC BY-NC-SA 4.0 协议进行许可

转载请注明出处:https://ikarp.top/index.php/archives/868.html

标签: ClickHouse

0 评论