ClickHouse是目前最流行的OLAP(联机分析处理)数据库之一。
它以查询速度极快著称,同样的查询,在MySQL里可能要几分钟,在ClickHouse里可能只要几秒钟。但要用好ClickHouse,光靠它本身的性能是不够的,还需要掌握正确的优化方法。
很多人用ClickHouse,建表随便建,查询随便写,结果发现性能并不像传说中那么快。其实,只要掌握了基本的优化方法,ClickHouse的性能就能提升好几倍。
本文是一份ClickHouse优化入门指南,从零开始,包括表引擎选择、建表优化、查询优化、配置调优、常见问题等。帮你快速掌握ClickHouse优化的核心方法。
一、ClickHouse是什么
先简单介绍一下ClickHouse。
ClickHouse是俄罗斯Yandex公司开源的列式数据库,专门用于OLAP场景。它的特点:
- 列式存储:按列存储数据,查询时只读取需要的列,IO少
- 向量化执行:批量处理数据,CPU利用率高
- 并行处理:充分利用多核CPU
- 压缩率高:列式存储+压缩,节省存储空间
- 查询速度快:亿级数据秒级查询
ClickHouse适合的场景:
- 日志分析
- 数据仓库
- 实时分析
- BI报表
- 监控系统
不适合的场景:
- 事务性操作(OLTP)
- 频繁的单行更新删除
- 小数据量(用MySQL就行)
二、表引擎选择
表引擎是ClickHouse的核心,不同的引擎有不同的特点。选对引擎,是优化的第一步。
1. MergeTree系列
这是ClickHouse最常用的引擎系列,适合大多数场景。
- MergeTree:最基础的引擎,支持按主键排序、分区、复制
- ReplacingMergeTree:支持去重,相同主键的行,保留最新的一条
- SummingMergeTree:自动聚合数值列,相同主键的行,数值列自动求和
- AggregatingMergeTree:存储聚合函数的中间状态,适合预聚合
- CollapsingMergeTree:用正负行折叠,支持更新删除
- VersionedCollapsingMergeTree:带版本的折叠引擎
怎么选:
- 普通查询:用MergeTree
- 需要去重:用ReplacingMergeTree
- 需要预聚合:用SummingMergeTree或AggregatingMergeTree
- 需要更新删除:用CollapsingMergeTree(但不推荐频繁更新)
2. Log系列
- TinyLog:最简单的引擎,没有索引,适合小表
- StripeLog:比TinyLog好一点,有简单的索引
- Log:有索引,支持并发读取
Log系列适合临时表、小表,不适合生产环境的大表。
3. 其他引擎
- Memory:数据存在内存里,速度极快,但重启就没了。适合临时数据、字典表
- Distributed:分布式引擎,把查询分发到多个节点
- Buffer:缓冲引擎,先写内存,再批量刷到目标表
- Dictionary:字典引擎,存储键值对,查询快
- Merge:合并多个表的数据,不存储数据
入门建议: 大多数场景用MergeTree就够了。等熟悉了,再根据需要选择其他引擎。
三、建表优化
建表是ClickHouse优化最重要的环节。表建得好,查询自然快。
1. 分区键(PARTITION BY)
分区是把数据按某个键分成不同的目录,查询时只扫描需要的分区,大大减少数据量。
常用的分区键:
- 按日期分区:toYYYYMMDD(createdat) 或 toYYYYMM(createdat)
- 按地区分区:region
- 按业务线分区:business_line
注意:
- 分区不要太细:按天分区就够了,不要按小时分区,分区太多影响性能
- 分区键要和查询条件对应:查询时经常用什么条件过滤,就用什么做分区键
- 分区键不要用高基数的列:比如用user_id分区,会有几百万个分区,性能很差
2. 排序键(ORDER BY)
排序键决定了数据在磁盘上的存储顺序。查询时,如果条件包含排序键的前缀,就能快速定位数据。
排序键的设计原则:
- 把经常用于过滤的列放在前面
- 把基数高的列放在前面(比如user_id比gender基数高)
- 排序键的前缀要和查询条件匹配
- 排序键不要太长,一般1-3列就够了
例子:
ORDER BY (date, user_id, event_type)这样,查询时如果有date、userid、eventtype的条件,就能利用排序键快速定位。
3. 主键(PRIMARY KEY)
ClickHouse的主键不是唯一约束,而是索引。主键必须是排序键的前缀。
一般情况下,主键和排序键的前缀一致就行,不用单独设置。
4. 数据类型选择
选择合适的数据类型,能节省存储空间,提升查询速度。
- 整数:用最小的够用的类型。比如年龄用UInt8(0-255),不要用Int32
- 字符串:用String,不要用FixedString(除非定长)
- 日期时间:用DateTime或Date,不要用字符串存时间
- 浮点数:用Float64,精度高;如果是金额,用Decimal
- 枚举:用Enum类型,比String省空间,查询更快
- 数组:用Array类型,适合一对多的场景
5. TTL(生存时间)
如果数据不需要永久保存,可以设置TTL,自动删除过期数据。
TTL created_at + INTERVAL 30 DAY这样,30天前的数据会自动删除,节省存储空间。
6. 建表示例
一个优化后的建表语句:
CREATE TABLE user_events (
event_date Date,
user_id UInt64,
event_type String,
event_time DateTime,
platform Enum('ios' = 1, 'android' = 2, 'web' = 3),
duration UInt32,
properties String
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id, event_type)
TTL event_date + INTERVAL 90 DAY四、查询优化
建表优化之后,查询优化也很重要。
1. 只查需要的列
ClickHouse是列式存储,只查需要的列,能大大减少IO。
-- 好
SELECT user_id, event_type FROM user_events WHERE event_date = '2022-05-01'
-- 不好
SELECT * FROM user_events WHERE event_date = '2022-05-01'永远不要用SELECT *。
2. 利用分区和排序键
查询条件要包含分区键和排序键的前缀,这样ClickHouse能跳过不需要的数据。
-- 好:利用了分区键event_date和排序键前缀
SELECT * FROM user_events
WHERE event_date = '2022-05-01' AND user_id = 123
-- 不好:没有利用分区键,全表扫描
SELECT * FROM user_events WHERE user_id = 1233. 用LIMIT
测试查询时,加LIMIT,避免全表扫描。
SELECT * FROM user_events LIMIT 104. 用PREWHERE
PREWHERE是ClickHouse特有的语法,比WHERE更高效。
PREWHERE会先只读取过滤列,过滤后再读取其他列。这样能减少IO。
SELECT * FROM user_events
PREWHERE event_date = '2022-05-01'
WHERE user_id = 123ClickHouse会自动把合适的条件转成PREWHERE,但手动写更明确。
5. 聚合优化
聚合查询是ClickHouse的强项,但也要注意:
- 分组的列尽量用排序键的列
- 用approximate函数(如uniqExact、quantile)代替精确函数,速度更快
- 大表的聚合,可以用预聚合表(SummingMergeTree)
-- 精确去重,慢
SELECT count(DISTINCT user_id) FROM user_events
-- 近似去重,快很多,误差很小
SELECT uniq(user_id) FROM user_events6. 避免在列上做函数运算
在查询条件中,不要在列上做函数运算,这样用不到索引。
-- 不好:在列上做函数,用不到索引
SELECT * FROM user_events WHERE toDate(event_time) = '2022-05-01'
-- 好:直接用列比较
SELECT * FROM user_events WHERE event_time >= '2022-05-01' AND event_time < '2022-05-02'7. JOIN优化
ClickHouse的JOIN性能不如单表查询,要注意:
- 小表JOIN大表,小表放右边
- 用GLOBAL JOIN处理分布式JOIN
- 尽量用字典(Dictionary)代替JOIN
- 能避免JOIN就避免,比如把需要的字段冗余到大表里
五、配置调优
除了建表和查询,配置调优也能提升性能。
1. 内存配置
ClickHouse是内存密集型的,内存配置很重要。
- maxmemoryusage:单个查询的最大内存,建议设置为物理内存的一半
- maxmemoryusageforall_queries:所有查询的总内存
- maxbytesbeforeexternalgroup_by:聚合时超过这个值,就用外部聚合(写磁盘)
2. 并发配置
- maxconcurrentqueries:最大并发查询数,根据CPU核数设置
- max_threads:单个查询的最大线程数,一般等于CPU核数
3. 存储配置
- 用SSD,不要用机械硬盘
- 数据目录单独挂盘
- 多块盘可以配置多个path,ClickHouse会自动负载均衡
4. Merge配置
- maxsuspiciousbroken_parts:允许的坏part数量
- partstothrow_insert:part太多时,拒绝写入
- 定期OPTIMIZE,合并小part
六、常见问题和解决方法
说说常见的问题和解决方法。
问题一:查询慢
可能的原因:
- 没有利用分区和排序键 → 检查查询条件,确保包含分区键和排序键前缀
- SELECT * → 只查需要的列
- 数据量太大 → 增加分区,设置TTL,用预聚合
- 配置太低 → 增加内存,用SSD
问题二:写入慢
可能的原因:
- 小批量频繁写入 → 攒批量写入,每次1000-10000条
- 并发写入太多 → 控制写入并发,2-4个就够了
- part太多 → 合并小part,调整Merge配置
问题三:Merge积压
可能的原因:
- 写入太频繁 → 减少写入频率,增加批量
- 分区太细 → 减少分区粒度
- 磁盘IO不够 → 用SSD,增加磁盘
问题四:内存不足
可能的原因:
- 查询太复杂 → 优化查询,减少JOIN和聚合
- 并发太高 → 降低并发
- 数据量太大 → 增加内存,或用外部聚合
七、监控和运维
优化不是一次性的,需要持续监控。
1. 监控指标
- 查询延迟:平均、P95、P99
- 吞吐量:每秒查询数、每秒写入行数
- 资源利用率:CPU、内存、磁盘IO
- Merge状态:part数量、Merge速度
- 错误率:查询失败率
2. 常用运维命令
-- 查看表的part信息
SELECT * FROM system.parts WHERE table = 'user_events'
-- 查看正在运行的查询
SELECT * FROM system.processes
-- 查看合并任务
SELECT * FROM system.merges
-- 优化表(合并part)
OPTIMIZE TABLE user_events
-- 查看表结构
DESCRIBE TABLE user_events3. 数据备份
ClickHouse的数据要定期备份。可以用:
- 冷备:复制数据目录
- 热备:用ALTER TABLE ... FREEZE
- 逻辑备份:clickhouse-dump
八、学习路径
如果你是ClickHouse新手,建议按这个路径学习:
- 基础:安装ClickHouse,建表、插入、查询,熟悉基本语法
- 表引擎:了解MergeTree系列,学会选择合适的引擎
- 建表优化:学习分区、排序键、数据类型选择
- 查询优化:学习SQL优化,利用分区和索引
- 配置调优:了解重要配置,根据场景调整
- 分布式:学习集群搭建、分布式表、副本
- 实战:在实际项目中应用,遇到问题解决问题
九、推荐资源
学习ClickHouse,推荐这些资源:
- 官方文档:最权威的资料,一定要读
- ClickHouse中文社区:国内的社区,有很多中文资料
- 《ClickHouse原理解析与应用实践》:不错的中文书
- ClickHouse博客:官方博客,有很多技术文章
- GitHub:看源码,深入理解
十、写在最后
ClickHouse是一个非常强大的OLAP数据库,但它的性能不是凭空来的。需要正确的建表、合理的查询、合适的配置,才能发挥它的威力。
本文介绍了ClickHouse优化的入门知识,包括表引擎选择、建表优化、查询优化、配置调优等。掌握了这些,你的ClickHouse查询速度就能提升好几倍。
当然,优化是一个持续的过程。随着数据量的增长和业务的变化,需要不断地监控和调整。
2022年了,数据越来越多,对数据分析的要求也越来越高。ClickHouse作为一款优秀的OLAP数据库,值得每一个数据工程师学习和掌握。
最后,用一句话总结:"ClickHouse优化的核心,是减少数据扫描量——分区、排序、列裁剪,都是为了这个目标。"
祝大家的ClickHouse查询都能飞起来。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录