3. ClickHouse 快速入门:架构原理、单机部署、建表与基本查询

好,咱们进入 ClickHouse 的世界。这一章我会带你快速上手,从架构原理到实际建表查询,一步到位。我个人习惯是「先理解设计,再动手操作」,这样遇到问题才不会慌。

3.1 ClickHouse 的架构原理

ClickHouse 为什么这么快?说白了,它的设计哲学就是「把每一分硬件性能都榨干」。我刚开始接触时也觉得夸张,直到有一次在项目中用 MySQL 跑百亿级聚合查询,等了 20 分钟没出结果……换成 ClickHouse 后,同样的数据量,3 秒出数。嗯,从那以后我就再也不敢小看列式存储了。

3.1.1 列式存储 vs 行式存储

传统数据库(比如 MySQL)是行式存储,一行数据物理上挨着放。你想想看,如果我只想查某个字段的平均值,它却要把整行数据都读出来,IO 浪费很大。

ClickHouse 采用列式存储,每一列单独存放。查询时只读取需要的列,IO 量骤减。再加上列式数据天然适合压缩,压缩比通常能达到 5:1 甚至 10:1。

特性 行式存储(MySQL) 列式存储(ClickHouse)
数据读取 读取整行 只读需要的列
压缩效率 较低 极高(同列数据类型一致)
适用场景 OLTP(频繁增删改) OLAP(批量查询、聚合分析)
典型延迟 毫秒级(单行) 秒级(大范围扫描)

3.1.2 核心架构组件

ClickHouse 的架构其实不复杂,核心就几个角色。我画了张图帮你理清关系。

ClickHouse 核心架构图 客户端 / JDBC / HTTP ClickHouse Server(协调节点) 数据节点 1 数据节点 2 数据节点 3 MergeTree 存储引擎(列式 + 分区 + 排序) 说明: 协调节点负责 SQL 解析、查询计划生成;数据节点负责实际数据存储与计算。 MergeTree 是 ClickHouse 最核心的存储引擎,支持分区、排序、主键索引。

核心要点:ClickHouse 的查询速度来源于「列式存储 + 向量化执行 + 稀疏索引」三驾马车。缺一个,性能都会大打折扣。

3.2 单机部署

部署 ClickHouse 其实很简单。我建议你直接用官方 RPM 包或者 Docker,省去编译的麻烦。这里我以 CentOS 7 为例,走一遍流程。

3.2.1 安装步骤

  1. 添加官方仓库
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://packages.clickhouse.com/rpm/clickhouse.repo
  1. 安装服务端和客户端
sudo yum install -y clickhouse-server clickhouse-client
  1. 启动服务
sudo systemctl start clickhouse-server
sudo systemctl enable clickhouse-server
  1. 验证连接
clickhouse-client --host 127.0.0.1 --port 9000

如果看到 ClickHouse server version 24.x.x 的提示,说明部署成功。

小技巧:我第一次部署时忘了开防火墙端口,结果客户端连不上。记得检查 9000(TCP)和 8123(HTTP)端口是否开放。

3.2.2 配置文件快速了解

ClickHouse 的主配置文件在 /etc/clickhouse-server/config.xml。我个人习惯先改两个地方:

  • 数据存储路径:默认在 /var/lib/clickhouse,如果磁盘空间不够,可以改到挂载盘。
  • 监听地址:默认只监听 127.0.0.1,如果想远程访问,改成 0.0.0.0。

注意:生产环境不要直接暴露 ClickHouse 到公网。建议通过内网访问,或者用 Nginx 做反向代理加认证。

3.3 建表与基本查询

好,服务跑起来了,咱们开始建表。ClickHouse 的建表语法和 MySQL 有点像,但细节上差别很大。

3.3.1 创建数据库

CREATE DATABASE IF NOT EXISTS olap_demo;

3.3.2 创建 MergeTree 表

MergeTree 是 ClickHouse 最核心的表引擎。我建议你一开始就用它,别碰那些乱七八糟的引擎。

CREATE TABLE olap_demo.order_events (
    event_time   DateTime,
    user_id      UInt32,
    order_id     UInt64,
    amount       Float64,
    status       String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, user_id);

这里有几个关键点:

  • PARTITION BY:按月分区,查询时能快速跳过无关分区。
  • ORDER BY:排序键,也是稀疏索引的依据。查询条件尽量匹配排序键的前缀。

避坑指南:我曾经在项目中把 ORDER BY 设成了 (user_id, event_time),结果按时间范围查询时索引失效,全表扫描。后来改成 (event_time, user_id),查询速度提升了 10 倍。排序键的顺序很重要!

3.3.3 插入数据

ClickHouse 支持批量插入,单条插入性能很差。我一般用 CSV 文件批量导入。

INSERT INTO olap_demo.order_events VALUES
('2024-01-15 10:30:00', 1001, 50001, 299.00, 'completed'),
('2024-01-15 10:35:00', 1002, 50002, 159.00, 'pending'),
('2024-01-16 09:20:00', 1001, 50003, 89.90, 'completed');

3.3.4 基本查询

来几个典型查询,感受一下 ClickHouse 的威力。

查询某天所有订单

SELECT * FROM olap_demo.order_events
WHERE event_time >= '2024-01-15' AND event_time < '2024-01-16';

按用户统计消费总额

SELECT user_id, sum(amount) AS total_amount
FROM olap_demo.order_events
GROUP BY user_id
ORDER BY total_amount DESC;

按月统计订单数量

SELECT toYYYYMM(event_time) AS month, count(*) AS order_count
FROM olap_demo.order_events
GROUP BY month
ORDER BY month;

你想想看,同样的查询在 MySQL 里跑百万级数据可能就要几秒,ClickHouse 在亿级数据下依然能秒出。这就是列式存储 + 向量化执行的优势。

3.3.5 常用函数速览

函数 作用 示例
count() 计数 count(DISTINCT user_id)
sum() 求和 sum(amount)
avg() 平均值 avg(amount)
uniq() 近似去重计数 uniq(user_id)
toYYYYMM() 提取年月 toYYYYMM(event_time)

经验之谈:ClickHouse 的 uniq() 函数用的是 HyperLogLog 算法,误差在 1% 左右,但速度极快。如果要求精确去重,用 count(DISTINCT ...),但性能会差一些。我在做用户画像时,通常用 uniq() 做快速估算,精确值留到离线计算。

3.4 本章小结