Skip to main content
  1. posts/

ClickHouse核心原理深度剖析

·7116 words·15 mins

1. ClickHouse是什么?

ClickHouse 是一个开源的列式存储数据库管理系统(DBMS),专为在线分析处理(OLAP)场景设计。由俄罗斯的 Yandex 公司于 2016 年开源,目前已成为最快的 OLAP 数据库之一。

1.1 核心特点

  • 极致性能:单表查询可达每秒数十亿行,聚合查询速度是传统数据库的 100-1000 倍
  • 列式存储:按列存储数据,大幅提高分析查询的效率和压缩率
  • 向量化执行:利用 CPU SIMD 指令并行处理数据
  • 分布式架构:支持数据分片和副本,可线性扩展
  • 实时写入:支持高吞吐的实时数据写入
  • SQL 支持:使用标准 SQL 语法,学习成本低

1.2 核心组件

正在加载图表...

1.3 适用场景

✅ 适用场景:

  • 大规模数据分析和报表
  • 实时数据仪表盘
  • 日志分析系统
  • 用户行为分析
  • 时序数据存储
  • 监控指标存储

❌ 不适用场景:

  • 高并发的点查询(Key-Value 查询)
  • 频繁的更新和删除操作
  • 事务性操作(OLTP)
  • 高度规范化的关系型数据

1.4 与其他数据库对比

特性ClickHouseMySQLElasticsearchDruid
存储方式列式行式文档列式
查询性能极快中等
写入性能中等中等
SQL支持完整完整有限有限
分布式原生支持需中间件原生支持原生支持
压缩率10-40倍2-3倍3-5倍10-20倍
事务支持完整

2. ClickHouse解决什么问题?

2.1 传统数据库的局限性

在大数据分析场景下,传统的行式数据库(如 MySQL、PostgreSQL)存在以下问题:

  1. 查询性能瓶颈

    • 分析查询通常只需要少数几列,但行式存储需要读取整行数据
    • 扫描大表时 I/O 开销巨大
    • 聚合计算效率低下
  2. 存储空间浪费

    • 行式存储压缩率低
    • 相同类型数据分散存储,无法充分压缩
    • 存储成本高
  3. 扩展性限制

    • 单机性能达到瓶颈后难以扩展
    • 分库分表复杂度高
    • 跨库查询困难

2.2 OLAP 场景的特点

OLAP(在线分析处理)场景具有以下特点:

  • 读多写少:查询频率远高于写入频率
  • 批量写入:数据通常批量导入,而非单条插入
  • 宽表查询:表的列数多,但单次查询只涉及少数列
  • 大范围扫描:经常需要扫描数百万甚至数十亿行数据
  • 聚合计算:大量的 SUM、AVG、COUNT 等聚合操作
  • 时序特性:数据通常按时间顺序写入,按时间范围查询

2.3 核心需求

  1. 极致的查询性能:秒级甚至毫秒级响应海量数据的分析查询
  2. 高效的数据压缩:降低存储成本,提高 I/O 效率
  3. 灵活的扩展能力:支持水平扩展,处理 PB 级数据
  4. 实时数据写入:支持高吞吐的实时数据导入
  5. 低维护成本:自动化的数据管理和优化

3. 怎么解决的?

ClickHouse 通过一系列精巧的设计解决了上述问题:

3.1 列式存储

核心思想:按列存储数据,而非按行。

正在加载图表...

优势

  • 分析查询只读取需要的列,减少 I/O
  • 相同类型数据连续存储,压缩率高
  • 向量化处理效率高

3.2 稀疏索引

传统数据库使用 B+ 树等密集索引,需要为每行数据建立索引项。ClickHouse 使用稀疏索引

  • 只为部分数据块建立索引(默认每 8192 行一个索引)
  • 索引占用空间小,可完全加载到内存
  • 通过索引快速定位数据块,然后顺序扫描块内数据

3.3 向量化执行

利用 CPU 的 SIMD 指令,一次处理多个数据:

传统方式:for (int i = 0; i < n; i++) sum += data[i];
向量化:  一次处理 16 个数据,速度提升 10-100 倍

3.4 数据分片与副本

  • 分片(Shard):将数据水平切分到多个节点,提高并行处理能力
  • 副本(Replica):每个分片有多个副本,保证高可用
  • ZooKeeper 协调:通过 ZooKeeper 管理副本同步和一致性

3.5 后台 Merge 机制

  • 写入时数据先保存为小文件(Part)
  • 后台线程异步合并小文件为大文件
  • Merge 过程中进行排序、去重、聚合等操作
  • 保证写入性能的同时优化查询效率

4. 核心流程

4.0 ClickHouse 整体工作流程

正在加载图表...

全局流程说明

ClickHouse 的工作流程分为两大核心流程:

  1. 查询流程

    • SQL 解析生成抽象语法树(AST)
    • 查询优化器进行谓词下推、列裁剪、分区裁剪等优化
    • 执行引擎构建 Pipeline,采用向量化并行处理
    • 存储引擎通过稀疏索引快速定位数据块,只读取需要的列
    • 返回结果给客户端
  2. 写入流程

    • 批量数据写入,按排序键排序
    • 创建新的 Data Part(数据分片)
    • 对数据进行压缩并生成索引
    • 后台 Merge 线程异步合并小 Parts 为大 Parts
    • 优化存储结构,提高查询效率

4.1 底层存储结构

MergeTree 引擎数据组织

正在加载图表...

数据文件结构

一个 Part 包含以下文件:

partition_date_min_max_level/
├── columns.txt          # 列信息
├── count.txt           # 行数
├── primary.idx         # 主键索引(稀疏索引)
├── partition.dat       # 分区键值
├── checksums.txt       # 校验和
├── column1.bin         # 列1的数据文件
├── column1.mrk         # 列1的标记文件
├── column2.bin         # 列2的数据文件
├── column2.mrk         # 列2的标记文件
└── ...

4.2 数据写入流程

正在加载图表...

写入流程详解

  1. 数据接收与解析

    • 客户端发送 INSERT 语句
    • 解析数据,计算分区键确定数据所属分区
  2. Block 构建

    • 在内存中构建 Block(批量数据块)
    • 按照 ORDER BY 键对数据排序
    • 计算主键索引(每 8192 行一个)
  3. 数据压缩

    • 对每列数据应用压缩算法(LZ4、ZSTD 等)
    • 列式存储天然具有高压缩率
  4. Part 写入

    • 为每列创建 .bin 文件(压缩后的数据)
    • 创建 .mrk 文件(标记文件,记录数据块偏移)
    • 创建 .idx 文件(稀疏主键索引)
    • 写入元数据文件(列信息、行数、校验和等)
  5. 原子性提交

    • 先写入临时目录
    • 所有文件写入完成后,原子性重命名为正式 Part
    • 保证数据一致性
  6. 触发 Merge

    • 通知后台 Merge 调度器
    • 异步检查是否需要合并 Parts

4.3 数据查询流程

正在加载图表...

查询流程详解

  1. SQL 解析

    • 解析 SQL 语句生成抽象语法树(AST)
    • 语义分析,验证表、列是否存在
  2. 查询优化

    • 列裁剪:只读取 SELECT 和 WHERE 中涉及的列
    • 谓词下推:将过滤条件下推到存储层
    • 分区裁剪:根据分区键过滤不相关的分区
  3. Part 选择

    • 列出所有相关分区的 Parts
    • 过滤掉明显不包含目标数据的 Parts
  4. 索引查找

    • 读取 primary.idx(稀疏索引)
    • 二分查找定位可能包含数据的 Granule(数据块)
    • 每个 Granule 默认 8192 行
  5. 列数据读取

    • 通过 .mrk 文件定位 .bin 文件中的偏移位置
    • 只读取需要的列(city 和 age)
    • 读取、解压缩、应用 WHERE 条件过滤
    • 向量化处理,利用 SIMD 指令加速
  6. 聚合计算

    • GROUP BY 分组
    • 计算聚合函数(COUNT、SUM 等)
    • 返回结果给客户端

4.4 Merge 合并流程

正在加载图表...

Merge 机制详解

  1. 触发条件

    • Part 数量过多(默认 > 8 个)
    • Part 大小不均衡
    • Level 层级需要合并
    • 手动触发 OPTIMIZE TABLE
  2. 选择策略

    • Level 合并:同一层级的 Parts 合并
    • 大小平衡:优先合并大小相近的 Parts
    • 时间窗口:合并同一时间范围的数据
  3. 合并过程

    • 并行读取多个源 Parts 的列数据
    • N 路归并排序(类似归并排序算法)
    • 执行引擎特定的逻辑:
      • ReplacingMergeTree:保留最新版本,去重
      • SummingMergeTree:对数值列求和
      • AggregatingMergeTree:合并聚合状态
    • 写入新的 Part,Level 加 1
  4. 原子性提交

    • 新 Part 写入完成后,原子性更新元数据
    • 删除旧 Parts(标记为删除,延迟物理删除)
    • 查询自动读取最新的 Parts
  5. 性能影响

    • Merge 在后台异步执行,不阻塞查询和写入
    • 合并大 Parts 时可能消耗较多 I/O 和 CPU
    • 可通过参数控制 Merge 的并发度和速度

5. ClickHouse 存在的问题与挑战

5.1 更新和删除操作的代价

问题描述

  • ClickHouse 不支持直接的 UPDATE 和 DELETE 操作
  • 需要通过 ALTER TABLE … DELETE 或 ALTER TABLE … UPDATE(Mutation)
  • Mutation 会重写整个 Part,代价非常高

影响场景

  • 需要频繁更新或删除数据的场景
  • GDPR 等合规场景需要立即删除用户数据

可视化展示

正在加载图表...

缓解措施

  • 使用 ReplacingMergeTree 实现逻辑删除(标记删除列)
  • 定期重建表,而非频繁删除
  • 设计表结构时避免需要更新的场景
  • 使用分区,删除整个分区而非单行

5.2 Join 性能问题

问题描述

  • ClickHouse 的 Join 性能相对较弱
  • 大表 Join 可能导致内存溢出
  • 右表会完全加载到内存

影响场景

  • 多表关联查询
  • 维度表关联
  • 实时 Join 场景

问题示意

正在加载图表...

缓解措施

  • 使用字典(Dictionary)替代小维度表 Join
  • 预先将维度列嵌入事实表(宽表模式)
  • 使用 Global Join(分布式场景)
  • 控制右表大小,必要时分多次 Join
  • 使用 join_use_nulls 等参数优化

5.3 实时性与一致性权衡

问题描述

  • 写入的数据不是立即可见(最终一致性)
  • 刚写入的 Part 需要时间才能被查询到
  • 副本之间存在同步延迟

影响场景

  • 需要强一致性的场景
  • 实时写入后立即查询
  • 高频写入 + 实时查询

缓解措施

  • 使用 SELECT ... FINAL 查询最新数据(性能较差)
  • 合理设置 min_insert_block_size_rows 参数
  • 使用 ReplicatedMergeTree 保证副本一致性
  • 业务层容忍短暂的延迟

5.4 分布式查询的复杂性

问题描述

  • 分布式查询需要两阶段聚合
  • 数据倾斜导致某些节点成为瓶颈
  • 节点故障影响整体查询

影响场景

  • 集群规模大
  • 数据分布不均
  • 复杂的分布式查询

缓解措施

  • 合理选择分片键,避免数据倾斜
  • 使用 distributed_aggregation_memory_efficient 优化
  • 设置查询超时和重试机制
  • 监控节点负载,及时扩容

5.5 运维复杂度

问题描述

  • ZooKeeper 依赖(副本场景)
  • 集群配置复杂
  • 监控指标众多
  • 故障排查困难

影响场景

  • 中小团队运维能力有限
  • 缺乏专业 DBA
  • 集群规模大

缓解措施

  • 使用 ClickHouse Keeper 替代 ZooKeeper
  • 采用容器化部署(K8s Operator)
  • 使用开源监控方案(Prometheus + Grafana)
  • 建立完善的告警和备份机制
  • 参考官方最佳实践

6. 总结

6.1 ClickHouse 的优势

ClickHouse 通过以下设计实现了极致的 OLAP 性能:

  1. 列式存储:只读取需要的列,大幅减少 I/O,提高压缩率
  2. 稀疏索引:索引占用空间小,可完全加载到内存,查询快速定位
  3. 向量化执行:利用 SIMD 指令并行处理,查询速度提升数十倍
  4. 后台 Merge:异步合并数据,保证写入性能的同时优化查询效率
  5. 分布式架构:支持数据分片和副本,可线性扩展到 PB 级

这些机制使得 ClickHouse 成为最快的 OLAP 数据库之一,在大规模数据分析场景下具有显著优势。

6.2 需要注意的问题

尽管 ClickHouse 性能优异,但在使用时需要注意:

  1. 更新删除代价高:避免频繁的 UPDATE 和 DELETE 操作
  2. Join 性能弱:大表 Join 谨慎使用,优先考虑宽表或字典
  3. 最终一致性:容忍短暂的数据延迟
  4. 分布式复杂:合理选择分片键,避免数据倾斜
  5. 运维成本:需要专业的运维能力和监控体系

6.3 最佳实践建议

  • 表设计

    • 合理设置分区键(通常按日期)
    • 排序键选择高频查询字段
    • 优先使用宽表,减少 Join
    • 选择合适的 MergeTree 引擎(Replacing、Summing 等)
  • 写入优化

    • 批量写入(每批至少 1000 行)
    • 避免频繁小批量写入
    • 合理设置 Merge 参数
  • 查询优化

    • 使用 PREWHERE 替代 WHERE
    • 充分利用分区裁剪
    • 避免 SELECT *
    • 大表 Join 使用字典或预先聚合
  • 集群规划

    • 根据数据量和查询并发度规划节点数
    • 每个分片设置 2-3 个副本
    • 监控磁盘、内存、网络使用情况
    • 定期清理历史分区
  • 监控运维

    • 监控关键指标:查询延迟、写入吞吐、Merge 速度
    • 设置告警规则:磁盘使用率、查询失败率
    • 定期备份元数据
    • 制定故障恢复预案

ClickHouse 在 OLAP 场景下具有无可比拟的性能优势,但需要深入理解其工作原理和限制,才能充分发挥其潜力。通过合理的表设计、查询优化和运维管理,可以构建高效、稳定的大数据分析平台。