前言
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 被删除具体来说:
- 客户端批量提交数据。
- ClickHouse 根据表的排序键对数据进行排序。
- 数据被写入一个新的 Part。
- 每个 Part 包含独立的列文件、标记文件和索引。
- 后台任务会异步合并多个小 Part。
- 最终生成更大、更紧凑的数据 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 支持可选的跳数索引,例如:
minmaxsetbloom_filtertokenbf_v1ngrambf_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_idamountcreate_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 可能依次进行:
- 通过分区键排除无关月份。
- 通过排序键范围排除无关 Granule。
- 通过跳数索引进一步过滤。
- 只读取可能包含目标数据的数据块。
这也是 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)适合经常根据用户和时间范围进行查询的场景。
设计原则:
- 优先放置最常用的过滤字段。
- 优先考虑等值过滤和范围过滤组合。
- 注意字段顺序。
- 不要盲目追求最高区分度。
- 需要结合实际查询条件设计。
例如:
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 KEY 和 ORDER 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 会合并这些中间状态。
适用场景
- 每小时订单聚合
- 每日销售额聚合
- 多维度报表
- 用户行为统计
- 去重用户数
- 分位数统计
- 预聚合计算
这样可以避免每次查询都扫描和聚合原始明细表。
创建聚合表
字段类型必须使用 AggregateFunction 或 SimpleAggregateFunction。
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 对比
| 系统 | 主要定位 | 存储方式 | 主要索引或组织方式 | 适合场景 | 主要短板 |
|---|---|---|---|---|---|
| ClickHouse | OLAP | 列式存储、Partition、Part | 排序键、稀疏主键索引、Min-Max、跳数索引 | 日志分析、分组统计、实时报表、大规模聚合 | 单行更新成本高,不适合核心 OLTP 和高频点查 |
| MySQL | OLTP | InnoDB 行式存储 | B+Tree 聚簇索引、二级索引 | 高频点查、事务、订单、账户、业务系统 | 大规模扫描和复杂聚合性能相对较弱 |
| TiDB | 分布式 HTAP | TiKV 行存 + 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 的高性能主要来自以下几个方面:
- 列式存储,只读取需要的字段。
- 高效压缩,减少磁盘 I/O。
- 稀疏索引和跳数索引,快速跳过无关数据。
- 向量化执行,批量利用 CPU。
- 多线程和分布式并行查询。
- MergeTree 顺序写入与后台异步合并。
- 针对聚合、过滤、排序等 OLAP 操作进行优化。
在实际系统中,ClickHouse 最合适的定位通常是:
业务数据库负责交易和事务
+
ClickHouse 负责分析和统计一句话概括:
ClickHouse 不是为了快速修改某一行数据,而是为了快速分析数十亿行数据。