ClickHouse是高性能的列式OLAP数据库,支持海量数据的实时分析查询。
本文系统介绍ClickHouse的核心特性、架构设计和实战应用,帮助你构建实时数据分析平台。
| 特性 | 说明 |
|---|
| 列式存储 | 按列存储,压缩率高 |
| 向量化计算 | SIMD指令加速 |
| 并行处理 | 多线程并行 |
| 实时写入 | 支持高并发写入 |
| SQL支持 | 标准SQL语法 |
| 分布式 | 支持水平扩展 |
ClickHouse架构:
┌─────────────────────────────────────────────────────────────┐
│ 客户端层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ JDBC/ODBC │ │ HTTP │ │ Native TCP │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 服务层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Parser │ │ Optimizer │ │ Executor │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 存储层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ MergeTree │ │ 分布式表 │ │ 物化视图 │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
-- MergeTree引擎
CREATE TABLE events (
event_date Date,
event_time DateTime,
user_id UInt64,
event_type String,
properties String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (user_id, event_date)
TTL event_date + INTERVAL 1 YEAR;
-- ReplacingMergeTree (去重)
CREATE TABLE user_actions (
user_id UInt64,
action_date Date,
action_type String
) ENGINE = ReplacingMergeTree(action_date)
ORDER BY (user_id, action_type);
-- SummingMergeTree (聚合)
CREATE TABLE page_views (
page_date Date,
page_url String,
view_count UInt64,
unique_users UInt64
) ENGINE = SummingMergeTree((view_count, unique_users))
ORDER BY (page_date, page_url);
| 引擎 | 适用场景 |
|---|
| MergeTree | 通用场景,日志、事件 |
| ReplacingMergeTree | 去重场景 |
| SummingMergeTree | 聚合统计 |
| AggregatingMergeTree | 复杂聚合 |
| Distributed | 分布式查询 |
-- 事件宽表
CREATE TABLE events_wide (
event_date Date,
event_time DateTime,
user_id UInt64,
user_name String,
user_age UInt8,
user_city String,
event_type String,
event_page String,
event_action String,
device_type String,
device_os String,
properties String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (user_id, event_date, event_time)
SETTINGS index_granularity = 8192;
-- 实时聚合物化视图
CREATE MATERIALIZED VIEW daily_user_stats
ENGINE = SummingMergeTree()
PARTITION BY toYYYYMM(day)
ORDER BY (user_id, day)
AS SELECT
toDate(event_time) AS day,
user_id,
count() AS event_count,
uniqExact(event_type) AS event_types
FROM events
GROUP BY day, user_id;
-- 查询时使用FINAL
SELECT * FROM daily_user_stats FINAL WHERE day = '2026-09-01';
-- 主键索引
ORDER BY (user_id, event_date, event_time)
-- 二级索引 (跳数索引)
ALTER TABLE events ADD INDEX idx_event_type event_type TYPE set(0) GRANULARITY 4;
-- 布隆过滤器索引
ALTER TABLE events ADD INDEX idx_user_id user_id TYPE bloom_filter(0.01) GRANULARITY 4;
-- 按月分区
PARTITION BY toYYYYMM(event_date)
-- 按天分区
PARTITION BY toDate(event_date)
-- 自定义分区
PARTITION BY (toYYYYMM(event_date), event_type)
-- 避免SELECT *
SELECT user_id, event_type, count() FROM events GROUP BY user_id, event_type;
-- 使用PREWHERE
SELECT * FROM events PREWHERE event_type = 'click' WHERE user_id = 123;
-- 使用FINAL
SELECT * FROM user_actions FINAL WHERE user_id = 123;
-- 日志表
CREATE TABLE access_logs (
log_time DateTime,
user_id String,
ip String,
method String,
url String,
status UInt16,
response_time UInt32
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(log_time)
ORDER BY (log_time, user_id);
-- 实时统计
SELECT
toStartOfFiveMinutes(log_time) AS time_bucket,
count() AS total_requests,
avg(response_time) AS avg_response_time,
countIf(status >= 500) AS error_count
FROM access_logs
WHERE log_time >= now() - INTERVAL 1 HOUR
GROUP BY time_bucket
ORDER BY time_bucket;
-- 用户行为表
CREATE TABLE user_events (
event_date Date,
user_id UInt64,
event_type String,
page String,
duration UInt32
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (user_id, event_date);
-- 用户留存分析
WITH first_day AS (
SELECT user_id, min(event_date) AS first_date
FROM user_events
GROUP BY user_id
)
SELECT
dateDiff('day', first_date, event_date) AS days_since_first,
uniqExact(user_id) AS active_users
FROM user_events
JOIN first_day USING (user_id)
WHERE event_date >= first_date
GROUP BY days_since_first
ORDER BY days_since_first;
| 原则 | 说明 |
|---|
| 宽表优先 | 适当冗余避免JOIN |
| 分区合理 | 按时间分区,避免过多分区 |
| 索引适量 | 主键+二级索引 |
| 压缩优化 | 使用合适的压缩算法 |
| 问题 | 原因 | 解决方案 |
|---|
| 查询慢 | 数据量大 | 优化分区和索引 |
| 写入慢 | 批量太小 | 批量写入 |
| 存储大 | 压缩率低 | 优化数据类型 |