用途
ClickHouse 是一款列式数据库管理系统,面向在线分析处理(OLAP)场景。传统行式数据库擅长事务型工作负载,而 ClickHouse 在需要扫描并聚合数十亿行数据时表现突出,这得益于列式存储、向量化查询执行和激进的压缩策略。本技能围绕使用 ClickHouse 进行实时分析展开,涵盖部署、建表、写入、查询和客户端连接。
主要能力
- 列式存储与向量化执行:聚合查询可在毫秒级返回数百万行的结果。
- MergeTree 表引擎:支持主键索引、数据分区和后台自动合并,在持续写入下仍保持查询性能。
- ReplacingMergeTree 去重:在后台合并时按排序键折叠重复行,适合可能重复写入的摄取管道。
- 物化视图:在插入时转换数据,将聚合结果写入目标表,实现预计算汇总,无需外部调度器。
- 批量导入:支持从 CSV、JSON、Parquet 等文件直接导入。
- Node.js 客户端:官方 @clickhouse/client 包提供类型化、支持流式读取的客户端。
使用方式
使用 Docker 部署
使用官方 Docker 镜像可快速启动 ClickHouse 实例,同时获得服务器和内置的 clickhouse-client CLI。
docker run -d \
--name clickhouse-server \
-p 8123:8123 \
-p 9000:9000 \
-v clickhouse-data:/var/lib/clickhouse \
-v clickhouse-logs:/var/log/clickhouse-server \
clickhouse/clickhouse-server:latest
容器运行后,可通过原生客户端连接:
docker exec -it clickhouse-server clickhouse-client
也可通过 HTTP 接口查询,适合快速健康检查和轻量集成:
curl 'http://localhost:8123/' --data-binary 'SELECT version()'
表引擎与 MergeTree
ClickHouse 通过表引擎组织数据,MergeTree 是最常用的引擎。以下示例创建一个按月份分区、按事件时间和用户排序的事件表:
CREATE TABLE events (
event_id UUID DEFAULT generateUUIDv4(),
user_id UInt64,
event_name LowCardinality(String),
properties String,
created_at DateTime DEFAULT now()
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(created_at)
ORDER BY (created_at, user_id);
ORDER BY 子句定义主键并控制数据在磁盘上的物理排序方式。按这些列过滤或分组的查询会显著更快,因为 ClickHouse 可以跳过不匹配的数据颗粒。对于取值集合有限的高基数字符串列(如事件名称),LowCardinality(String) 会应用字典编码,大幅减少存储并加速分组查询。
使用 ReplacingMergeTree 去重
当摄取管道可能发送重复事件时,ReplacingMergeTree 会在后台合并时按排序键折叠相同行:
CREATE TABLE user_profiles (
user_id UInt64,
display_name String,
plan LowCardinality(String),
updated_at DateTime
) ENGINE = ReplacingMergeTree(updated_at)
ORDER BY user_id;
合并时按 updated_at 保留最新的一行。
插入数据
ClickHouse 针对批量插入进行了优化。逐行发送可行但浪费资源,建议每批至少几千行。
INSERT INTO events (user_id, event_name, properties) VALUES
(1001, 'page_view', '{"page": "/dashboard"}'),
(1001, 'button_click', '{"button": "export"}'),
(1002, 'page_view', '{"page": "/settings"}'),
(1003, 'signup', '{}');
从文件批量加载时,ClickHouse 可直接摄取 CSV、JSON 和 Parquet:
cat events.csv | docker exec -i clickhouse-server \
clickhouse-client --query="INSERT INTO events FORMAT CSVWithNames"
聚合查询
以下示例展示日活跃用户、最常用功能和留存队列分析:
-- 过去 30 天的日活跃用户
SELECT
toDate(created_at) AS day,
uniqExact(user_id) AS dau
FROM events
WHERE created_at >= now() - INTERVAL 30 DAY
GROUP BY day
ORDER BY day;
-- 本周使用最多的前 10 个功能
SELECT
event_name,
count() AS total,
uniq(user_id) AS unique_users
FROM events
WHERE created_at >= toMonday(now())
GROUP BY event_name
ORDER BY total DESC
LIMIT 10;
-- 留存队列:1 月注册且 7 天内回访的用户
SELECT
toDate(s.created_at) AS signup_date,
count(DISTINCT r.user_id) AS retained
FROM events s
INNER JOIN events r ON s.user_id = r.user_id
AND r.created_at BETWEEN s.created_at AND s.created_at + INTERVAL 7 DAY
AND r.event_name != 'signup'
WHERE s.event_name = 'signup'
AND s.created_at BETWEEN '2025-01-01' AND '2025-01-31'
GROUP BY signup_date
ORDER BY signup_date;
物化视图
ClickHouse 中的物化视图是插入时转换数据的触发器。它们从源表读取数据,将聚合结果写入目标表,无需外部调度器即可获得预计算汇总。
-- 每小时事件计数的目标表
CREATE TABLE hourly_event_counts (
hour DateTime,
event_name LowCardinality(String),
count UInt64,
unique_users UInt64
) ENGINE = SummingMergeTree()
ORDER BY (hour, event_name);
-- 每次向 events 表 INSERT 时填充 hourly_event_counts 的物化视图
CREATE MATERIALIZED VIEW mv_hourly_events
TO hourly_event_counts
AS SELECT
toStartOfHour(created_at) AS hour,
event_name,
count() AS count,
uniq(user_id) AS unique_users
FROM events
GROUP BY hour, event_name;
此后每次向 events 插入数据都会自动更新汇总。无论原始 events 表增长到多大,查询 hourly_event_counts 都近乎即时。
Node.js 客户端
官方 @clickhouse/client 包为 Node.js 应用提供类型化、支持流式读取的客户端。
// npm install @clickhouse/client
import { createClient } from '@clickhouse/client';
const client = createClient({
url: 'http://localhost:8123',
username: 'default',
password: '',
database: 'default',
});
// 批量插入事件
async function insertEvents(events) {
await client.insert({
table: 'events',
values: events,
format: 'JSONEachRow',
});
}
// 查询日活跃用户
async function getDailyActiveUsers(days = 30) {
const result = await client.query({
query: `
SELECT toDate(created_at) AS day, uniqExact(user_id) AS dau
FROM events
WHERE created_at >= now() - INTERVAL {days:UInt32} DAY
GROUP BY day ORDER BY day
`,
query_params: { days },
format: 'JSONEachRow',
});
return result.json();
}
// 使用
await insertEvents([
{ user_id: 1001, event_name: 'page_view', properties: '{"page":"/home"}' },
{ user_id: 1002, event_name: 'signup', properties: '{}' },
]);
const dau = await getDailyActiveUsers(7);
console.log('DAU last 7 days:', dau);
await client.close();
客户端通过 result.stream() 支持大结果集的流式读取,按块返回行而非将整个响应缓冲在内存中。对于高吞吐摄取,建议保持持久客户端实例,并按定时器或缓冲阈值批量插入。
适用场景
- 实时分析查询:在海量数据集上执行聚合、分组和过滤。
- 事件分析与用户行为统计:日活跃用户、功能使用排行、留存队列。
- 需要预计算汇总的仪表盘:通过物化视图实现近即时查询。
- 可能产生重复数据的摄取管道:使用 ReplacingMergeTree 去重。
限制
资料未提供关于并发限制、集群部署、权限管理、收费与价格、平台兼容性等具体信息,暂未提供。