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 = 123

3. 用LIMIT

测试查询时,加LIMIT,避免全表扫描。

SELECT * FROM user_events LIMIT 10

4. 用PREWHERE

PREWHERE是ClickHouse特有的语法,比WHERE更高效。

PREWHERE会先只读取过滤列,过滤后再读取其他列。这样能减少IO。

SELECT * FROM user_events 
PREWHERE event_date = '2022-05-01'
WHERE user_id = 123

ClickHouse会自动把合适的条件转成PREWHERE,但手动写更明确。

5. 聚合优化

聚合查询是ClickHouse的强项,但也要注意:

  • 分组的列尽量用排序键的列
  • 用approximate函数(如uniqExact、quantile)代替精确函数,速度更快
  • 大表的聚合,可以用预聚合表(SummingMergeTree)
-- 精确去重,慢
SELECT count(DISTINCT user_id) FROM user_events

-- 近似去重,快很多,误差很小
SELECT uniq(user_id) FROM user_events

6. 避免在列上做函数运算

在查询条件中,不要在列上做函数运算,这样用不到索引。

-- 不好:在列上做函数,用不到索引
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

六、常见问题和解决方法

说说常见的问题和解决方法。

问题一:查询慢

可能的原因:

  1. 没有利用分区和排序键 → 检查查询条件,确保包含分区键和排序键前缀
  2. SELECT * → 只查需要的列
  3. 数据量太大 → 增加分区,设置TTL,用预聚合
  4. 配置太低 → 增加内存,用SSD

问题二:写入慢

可能的原因:

  1. 小批量频繁写入 → 攒批量写入,每次1000-10000条
  2. 并发写入太多 → 控制写入并发,2-4个就够了
  3. part太多 → 合并小part,调整Merge配置

问题三:Merge积压

可能的原因:

  1. 写入太频繁 → 减少写入频率,增加批量
  2. 分区太细 → 减少分区粒度
  3. 磁盘IO不够 → 用SSD,增加磁盘

问题四:内存不足

可能的原因:

  1. 查询太复杂 → 优化查询,减少JOIN和聚合
  2. 并发太高 → 降低并发
  3. 数据量太大 → 增加内存,或用外部聚合

七、监控和运维

优化不是一次性的,需要持续监控。

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_events

3. 数据备份

ClickHouse的数据要定期备份。可以用:

  • 冷备:复制数据目录
  • 热备:用ALTER TABLE ... FREEZE
  • 逻辑备份:clickhouse-dump

八、学习路径

如果你是ClickHouse新手,建议按这个路径学习:

  1. 基础:安装ClickHouse,建表、插入、查询,熟悉基本语法
  2. 表引擎:了解MergeTree系列,学会选择合适的引擎
  3. 建表优化:学习分区、排序键、数据类型选择
  4. 查询优化:学习SQL优化,利用分区和索引
  5. 配置调优:了解重要配置,根据场景调整
  6. 分布式:学习集群搭建、分布式表、副本
  7. 实战:在实际项目中应用,遇到问题解决问题

九、推荐资源

学习ClickHouse,推荐这些资源:

  1. 官方文档:最权威的资料,一定要读
  2. ClickHouse中文社区:国内的社区,有很多中文资料
  3. 《ClickHouse原理解析与应用实践》:不错的中文书
  4. ClickHouse博客:官方博客,有很多技术文章
  5. GitHub:看源码,深入理解

十、写在最后

ClickHouse是一个非常强大的OLAP数据库,但它的性能不是凭空来的。需要正确的建表、合理的查询、合适的配置,才能发挥它的威力。

本文介绍了ClickHouse优化的入门知识,包括表引擎选择、建表优化、查询优化、配置调优等。掌握了这些,你的ClickHouse查询速度就能提升好几倍。

当然,优化是一个持续的过程。随着数据量的增长和业务的变化,需要不断地监控和调整。

2022年了,数据越来越多,对数据分析的要求也越来越高。ClickHouse作为一款优秀的OLAP数据库,值得每一个数据工程师学习和掌握。

最后,用一句话总结:"ClickHouse优化的核心,是减少数据扫描量——分区、排序、列裁剪,都是为了这个目标。"

祝大家的ClickHouse查询都能飞起来。