SuperAPI AI 工具集

clickhouse

数据与分析

查看项目

更新于 10-16 07:29

技能介绍

用途

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 去重。

限制

资料未提供关于并发限制、集群部署、权限管理、收费与价格、平台兼容性等具体信息,暂未提供。