clickhouse

通过列式存储、MergeTree 和向量化执行分析海量追加写数据

  • S:业务数据量爆炸式增长,日志、埋点、监控指标动辄百亿、千亿行,业务方希望对这些数据做实时多维分析、即席查询和报表。
  • C:传统的行式 OLTP 数据库(MySQL、PostgreSQL)面向事务设计,做大规模聚合扫描时 IO 放大严重、CPU 利用率低;Hadoop/Spark 这类离线方案延迟在分钟到小时级,无法满足交互式分析的体验;自研列存又成本高、生态弱。
  • Q:有没有一种数据库,既能像列式存储那样高效压缩与扫描海量数据,又能像 SQL 数据库那样易用,并且查询延迟做到亚秒级?
  • A:ClickHouse —— 通过列式存储 + 向量化执行 + MPP 并行 + LSM 结构的 MergeTree,在单机上就能跑出 GB/s 级别的扫描吞吐,对百亿行数据的聚合查询通常在秒级返回,且兼容标准 SQL,是当下 OLAP 场景的事实标准之一。

高性能、列式、面向 OLAP 的 SQL 数据库

ClickHouse 是一个列式 OLAP 数据库,主要用于大规模数据分析、实时报表、日志分析、用户行为分析、监控指标分析等场景。

从海量明细数据里,快速算聚合结果。

大量插入,少量更新,频繁聚合查询。

大量追加写 少量修改删除 频繁聚合查询

ClickHouse 的核心不是“一个很快的 SQL 数据库”,而是把 OLAP 查询转化成对 sorted + compressed + immutable columnar data 的高度并行扫描。

实现原理

核心思想:让 CPU 始终在对连续内存块做紧凑的、SIMD 友好的计算

一句话:少读 + 顺序读 + 批量算 + 多核并行 + 多机并行

  • 列式存储:每列单独成文件,类型一致带来高压缩比(LZ4/ZSTD 常见 5×~20×),查询只读用到的列。
  • MergeTree 引擎:数据按主键排序写入不可变 Part,后台持续 Merge 成大 Part(类 LSM Compaction)。
  • 稀疏主键索引:每 index_granularity(默认 8192 行)一条索引项常驻内存,配合分区裁剪、Skip Index(minmax/bloom 等)大量跳过数据。
  • 向量化执行:算子间传递 Block(默认 65536 行),按列循环 + SIMD + LLVM JIT,避开 Volcano 一次一行的开销。
  • MPP 并行:单机按 Part/Granule 多线程扫描;分布式表两阶段聚合(本地 partial → 全局 merge)减少跨节点数据。
  • 写入路径:append-only 大批次写一次成一个 Part,小批量会产生大量小 Part 拖垮 Merge;update/delete 是异步 Mutation,重写整段 Part。
  • 副本与一致性ReplicatedMergeTree 借 ZooKeeper/Keeper 异步多副本复制;事务能力弱(仅单 Part 原子),不适合 OLTP。

1.先理解列式数据库和 OLAP。 2.学会 MergeTree 建表。 3.重点理解 PARTITION BY 和 ORDER BY。 4.掌握常用聚合函数:count、uniq、sum、avg、quantile。 5.学会数据导入:CSV、JSON、Kafka、MySQL CDC。 6.学会物化视图做预聚合。 7.再看 Projection、Dictionary、分布式表、集群部署。

表设计核心:

  • ENGINE = MergeTree 决定表的存储能力
  • PARTITION BY ... 数据怎么分区管理
  • ORDER BY ... 数据在磁盘上怎么排序 直接影响查询性能

MergeTree 适合大批量写入、大规模查询、后台自动整理数据的表引擎。

项目 PARTITION BY ORDER BY
主要作用 数据分区管理 数据排序和索引
常见字段 时间 查询过滤字段
是否越细越好 不是 也不是,需匹配查询
常见写法 toYYYYMM(time) (type, time, id)
影响 删除、裁剪、管理 查询性能核心
  1. MergeTree 是 ClickHouse 最常用表引擎。
  2. PARTITION BY 通常按时间分区,比如月或天。
  3. 分区不要按 user_id、uuid、request_id 这类高基数字段。
  4. ORDER BY 是查询性能核心,不是结果排序。
  5. ORDER BY 要根据最常用 WHERE 条件设计。

聚合

函数 作用 常见用途
count() 统计行数 PV、请求量、订单量
uniq() 去重计数 UV、去重设备数
sum() 求和 销售额、流量
avg() 平均值 平均耗时、平均订单金额
min() 最小值 最早时间、最低价格
max() 最大值 最晚时间、最高金额
quantile() 分位数 P95、P99 延迟
topK() 高频值 热门页面、热门城市
countIf() 条件计数 错误数、点击数
sumIf() 条件求和 已支付金额、退款金额

写入

  1. 原则 1:批量写入 为大批量插入优化
  2. 原则 2:控制小 part 后台会合并 part,但如果小批次太多,后台合并压力会很大
  3. 原则 3:尽量按分区写入
  4. 原则 4:数据类型要对齐
  5. 原则 5:失败要能重试 写入链路最好设计成:应用 → 队列 → 消费者 → ClickHouse

物化视图:如何把慢查询变成快查询

把查询时的聚合计算,提前放到写入时完成。

Projection:表内投影,如何优化不同查询模式

同一张 ClickHouse 表内部,额外存了一份按另一种方式组织的数据(表内副本)

  • 物化视图是另一张显式目标表
  • Projection 是同一张表内部的隐藏优化结构
对比项 Materialized View Projection
存储位置 单独目标表 原表内部
查询方式 通常查目标表 仍然查原表
是否透明 不透明,需要你知道目标表 比较透明,优化器自动选择
适合 预聚合、ETL、复杂转换 多种排序、部分预聚合
是否支持 Join 定义 支持复杂一些 Projection 定义不支持 Join
是否支持 WHERE 过滤定义 可以 Projection 定义不支持 WHERE
TTL 灵活性 目标表可单独 TTL 不能和源表用不同 TTL
链式处理 可以链式物化视图 不支持链式

Dictionary 高性能 Key-Value 查询结构,常用来做维度表查询

一种来自内部或外部数据源的 key-value 表示,用于低延迟 lookup 查询,常用于优化 Join 或数据 enrichment

把维表变成一个快速查找结构,查询时用 key 去取属性,而不是每次做完整 Join

场景 更适合
事实表很大,维表较小 Dictionary
只需要根据 key 查几个字段 Dictionary
维表更新不需要秒级实时 Dictionary
复杂多条件 Join Join
多对多关系 Join
需要 Join 后保留大量维表字段 宽表或 Join
维度字段查询极高频 Dictionary 或宽表

Join 与宽表设计

宽表 就是把查询常用字段提前合并到一张事实表里

ClickHouse 偏向:查询时少 Join,写入时多处理,尽量让查询阶段扫一张大宽表。

ClickHouse Join 的一个重要规则:小表放右边, 很多 Join 算法会基于右表构建内存结构

场景 推荐方案
高频固定报表 宽表
事实表巨大,维表较小,按 key 查属性 Dictionary
维表很小 Join 也可以
临时分析 Join
多对多关系 Join 或中间明细表
维度变化慢,按事件发生时口径统计 宽表
维度变化快,按当前口径统计 Dictionary 或 Join
查询需要极低延迟 宽表或预聚合
查询字段很多,每次都要补很多维度 宽表
维表太大无法放内存 Join / Direct Dictionary / 宽表重建

TTL 与冷热数据管理

自动删除过期数据 自动冷热分层 自动清理大字段 辅助数据生命周期管理

自动删除过期数据 自动冷热分层 自动清理大字段 辅助数据生命周期管理

MergeTree 变体

ReplacingMergeTree / SummingMergeTree / AggregatingMergeTree

普通 MergeTree 存明细;ReplacingMergeTree 存最后一版;SummingMergeTree 存可累加指标;AggregatingMergeTree 存聚合状态

引擎 主要用途
ReplacingMergeTree 去重、保留最新版本
SummingMergeTree 自动把数值列求和
AggregatingMergeTree 存储聚合状态,适合复杂预聚合

为什么 数据写进去后,不会像 MySQL 那样频繁原地更新,而是通过后台 merge 慢慢合并数据。 也就是在merge时候做一些操作

数据更新与删除

追加写入 → 后台合并 → 查询时读取列式数据

我要写入一个新事实,让查询或后台 merge 得到新结果

不像 MySQL 那样频繁原地更新:ClickHouse 面向 OLAP,大量数据顺序写入、压缩、列式扫描更重要;原地更新会破坏这种高吞吐写入和列式存储优势。

性能优化基础

让查询少读数据,而不是读完以后算得更快

核心抽象: 从磁盘上很多有序数据块中,尽量少拿块; 优化(哪些块不用读)

第一步:看 query_log read_rows,read_bytes 第二步:看 SQL 访问模式 第三步:看表结构 第四步:EXPLAIN indexes = 1 第五步:决定优化手段

看执行计划、读行数、索引命中、避免全表扫

数据压缩与类型设计

LowCardinalityEnumDecimal、Nullable 使用建议

类型 特点 适合
LowCardinality(String) 灵活,自动字典编码 低基数但会变化的字符串
Enum8 / Enum16 严格,值集合固定 稳定状态机、固定枚举
String 最通用,但可能重 高基数或自由文本

分布式表与集群

三层概念:本地表、分片 shard、副本 replica、Distributed 表 shard 存不同部分的数据;replica 是相同数据的副本;Distributed 表本身不存数据,而是把查询转发到各个 shard,并汇总结果。

  • shard:分片,解决容量和吞吐扩展
  • replica:副本,解决高可用和读扩展
  • Distributed 表:提供一个统一查询入口
概念 数据关系 解决问题
shard 不同 shard 存不同数据 扩容、分摊计算、分摊写入
replica 同一 shard 的多个副本存相同数据 高可用、容灾、读扩展
组件 负责什么
Distributed 跨 shard 查询和写入路由
ReplicatedMergeTree 同一个 shard 内的副本复制
ON CLUSTER 在多个节点执行 DDL
Keeper / ZooKeeper 协调副本元数据和复制状态

ClickHouse 集群 = 多个本地 MergeTree 表 + Distributed 统一入口 + shard 分片 + replica 副本。

副本与高可用

副本主要解决:高可用,容灾,读扩展,故障恢复

ClickHouse 的复制是“表级别”的,同一个 server 上可以同时有复制表和非复制表。 这张表使用了 ReplicatedMergeTree 才有复制能力。

Keeper :副本复制的协调者 / 元数据日志中心 主要存:复制元数据,副本状态,复制队列,分布式 DDL 相关信息

ClickHouse 的复制更适合这样理解:面向 OLAP 的异步副本复制 更关注:高吞吐写入,大规模分析,最终一致,可用性;而不是:每次写后读都强一致

system.replicas 是监控副本状态的核心系统表

Kafka 接入

ClickHouse 有一个 Kafka 表引擎,把 Kafka topic 暴露成一张 ClickHouse 表

查询 Kafka Engine 表时,本质是在消费 Kafka 消息

你查一次
  ↓
ClickHouse 消费一批消息
  ↓
consumer offset 往前推进
  ↓
下次可能读不到同一批消息

Kafka Engine 表更适合搭配:Materialized View;让 MV 持续消费 Kafka,然后写入真正的 MergeTree 表

ClickHouse 接 Kafka,最常见是三张表:

  1. Kafka Engine 表:负责读 Kafka
  2. MergeTree 目标表:负责存真实数据
  3. Materialized View:负责把 Kafka 表的数据写入MergeTree目标表
Kafka topic: user_events
        ↓
kafka_events_queue  -- Kafka Engine 表,不长期存数据
        ↓
mv_kafka_to_events  -- Materialized View,消费并写入
        ↓
events_local        -- MergeTree 表,真正存数据

MySQL 同步到 ClickHouse

MySQL 是行级更新的 OLTP 源库,ClickHouse 是追加优先的 OLAP 分析库,中间通常靠 CDC变更流转成可分析数据

MySQL binlog 也是一种事实日志 CDC 是把数据库变更日志变成事件流 ClickHouse 消费这个事件流,生成分析表

CDC: Change Data Capture 变更数据捕获:捕获源数据库里发生了什么变化 CDC 把数据库内部的变化,变成外部系统可以消费的事件流。

MySQL binlog
  ↓
解析 INSERT / UPDATE / DELETE
  ↓
生成变更事件
  ↓
写入 Kafka 或直接写入 ClickHouse

Debezium 读取 MySQL binlog ,把 INSERT / UPDATE / DELETE 变成 Kafka 消息 Flink 清洗 / join / 聚合 / 宽表化 DataX 定时批量抽取 Airbyte 快速搭建数据同步

Debezium 偏“捕获变更” Flink 偏“处理变更” DataX 偏“批量搬运” Airbyte 偏“连接器化同步”

场景 方案
简单单表同步 Airbyte / DataX / ClickPipes / connector
MySQL binlog 进入 Kafka Debezium
多表 join、清洗、聚合 Flink CDC
离线批量导入 DataX / Spark / 自研批处理
高度定制同步语义 自研 consumer
需要复杂错误处理和回放 Kafka + Flink / 自研

监控与运维

system 表:小 part、慢 merge、副本延迟、磁盘快满,是 ClickHouse 运维四大高频问题

问题 主要看哪里
慢查询 system.query_log
当前正在跑的查询 system.processes
表的 part 是否过多 system.parts
merge 是否跟不上 system.merges
副本是否延迟 system.replicas
复制队列是否堆积 system.replication_queue
磁盘空间 system.disks
表大小 system.parts
mutation 是否卡住 system.mutations / system.merges
Kafka 消费是否异常 Kafka consumer lag + ClickHouse logs / Kafka 表状态

实战项目:用户行为分析系统

从建表、导入、预聚合到报表查询完整走一遍

  1. https://jreypo.io/2026/07/12/clickhouse-what-all-the-fuss-is-about/

评论