ClickHouse

返回 列式数据库

ClickHouse 是俄罗斯 Yandex 开源的列式分析数据库,专为 OLAP(联机分析处理) 场景设计,擅长对海量数据做实时聚合查询,单机每秒可处理数十亿行。


列式存储 vs 行式存储

维度行式(MySQL / PostgreSQL)列式(ClickHouse)
存储方式一行数据连续存储同一列数据连续存储
查询少量列需读取整行,I/O 浪费只读需要的列,I/O 极小
压缩比低(行内数据类型混杂)高(同列数据类型相同,重复度高)
写入方式单行写入效率高批量写入效率高,单行写入代价大
适用场景OLTP(事务处理)OLAP(分析查询)

核心特性

  • 向量化执行引擎:CPU 指令级并行,批量处理数据块而非逐行处理
  • 数据压缩:LZ4 / ZSTD 压缩,列存储压缩比通常达 10:1
  • 分布式查询:数据分片到多节点,查询自动并行执行
  • 近似计算uniqHLL12quantileTDigest 等近似函数,牺牲少量精度换极速
  • 物化视图:自动维护聚合结果,查询直接命中预计算数据

表引擎

ClickHouse 的表引擎决定数据存储和查询行为,生产环境最常用 MergeTree 家族

MergeTree(核心引擎)

CREATE TABLE events (
    event_date  Date,
    user_id     UInt64,
    action      String,
    amount      Float64,
    created_at  DateTime
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)   -- 按月分区
ORDER BY (event_date, user_id)       -- 排序键,同时是稀疏索引
PRIMARY KEY (event_date, user_id)    -- 可省略,默认等于 ORDER BY
SETTINGS index_granularity = 8192;  -- 每 8192 行一个索引点

ReplacingMergeTree(去重)

后台异步合并时去除重复数据,适合幂等写入场景(最终一致,不保证实时去重):

CREATE TABLE user_profiles (
    user_id    UInt64,
    name       String,
    updated_at DateTime
)
ENGINE = ReplacingMergeTree(updated_at)  -- 保留 updated_at 最大的版本
ORDER BY user_id;

SummingMergeTree(自动求和)

合并时自动对数值列求和,适合计数/累加场景:

CREATE TABLE page_views (
    site_id UInt32,
    page    String,
    date    Date,
    views   UInt64
)
ENGINE = SummingMergeTree(views)
ORDER BY (site_id, page, date);

AggregatingMergeTree(预聚合)

配合物化视图存储聚合中间状态,查询用 -Merge 函数合并:

CREATE MATERIALIZED VIEW uv_mv
ENGINE = AggregatingMergeTree()
ORDER BY (date, page)
AS SELECT
    date,
    page,
    uniqState(user_id) AS uv_state
FROM events
GROUP BY date, page;
 
-- 查询时合并中间状态
SELECT date, page, uniqMerge(uv_state) AS uv
FROM uv_mv
GROUP BY date, page;

ReplicatedMergeTree(高可用)

结合 ZooKeeper 实现多副本同步:

ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
ORDER BY (event_date, user_id);

数据类型

类型说明
UInt8/16/32/64无符号整数
Int8/16/32/64有符号整数
Float32/64浮点数
Decimal(P, S)精确小数,金融场景用
String变长字符串
Date / DateTime日期 / 时间
Array(T)数组,如 Array(String)
Nullable(T)可为 NULL,尽量避免(影响性能)
LowCardinality(T)低基数优化,枚举类字段推荐

LowCardinality(String)String 在低基数字段(性别、状态、地区)上查询快数倍。


索引机制

ClickHouse 的主键是稀疏索引,不同于 MySQL 的 B+ 树:

  • 每隔 index_granularity(默认 8192)行存一个索引点
  • 查询时定位到对应的数据块(granule),再扫描块内数据
  • 主键不保证唯一性,仅用于加速范围查询

跳数索引(Skip Index)

CREATE TABLE events (
    event_date Date,
    user_id    UInt64,
    city       String,
    INDEX idx_city city TYPE bloom_filter GRANULARITY 4
)
ENGINE = MergeTree()
ORDER BY (event_date, user_id);

常用查询

聚合分析

SELECT
    toDate(created_at)  AS date,
    count()             AS pv,
    uniq(user_id)       AS uv,
    sum(amount)         AS revenue
FROM events
WHERE created_at >= '2024-01-01'
  AND created_at <  '2024-02-01'
GROUP BY date
ORDER BY date;

漏斗分析

SELECT level, count() AS users
FROM (
    SELECT
        user_id,
        windowFunnel(86400)(
            created_at,
            action = 'register',
            action = 'view_product',
            action = 'add_cart',
            action = 'order'
        ) AS level
    FROM events
    WHERE event_date >= '2024-01-01'
    GROUP BY user_id
)
GROUP BY level
ORDER BY level;

Array 函数

-- 展开数组行(类似 UNNEST)
SELECT user_id, tag
FROM users
ARRAY JOIN tags AS tag;
 
-- 聚合为数组
SELECT user_id, groupArray(action) AS action_sequence
FROM events
GROUP BY user_id;

写入最佳实践

ClickHouse 不适合高频单行写入,频繁小批量写入会导致 Too many parts 错误。

做法说明
批量写入每批至少 1000 行,推荐 10w+ 行
Buffer 引擎内存缓冲后批量刷入主表
消息队列Kafka → ClickHouse,削峰填谷
避免频繁 UPDATE/DELETE用 ReplacingMergeTree 替代
-- Buffer 引擎示例
CREATE TABLE events_buffer AS events
ENGINE = Buffer(
    currentDatabase(), events,
    16,           -- 分片数
    10, 100,      -- 刷新时间:最短 10s,最长 100s
    10000, 1000000,          -- 刷新行数区间
    10000000, 1000000000     -- 刷新字节数区间
);

分布式部署

-- 分布式表:查询路由到各 shard,结果合并返回
CREATE TABLE events_distributed AS events
ENGINE = Distributed(
    cluster_name,
    currentDatabase(),
    events,      -- 本地表名
    rand()       -- 分片键
);

常见模式:写本地表,查分布式表

  • 写入时直连各节点写本地表,避免分布式写入的双倍网络开销
  • 查询时查分布式表,自动跨节点聚合

与 MySQL 数据同步

生产中常见架构:MySQL(OLTP)→ ClickHouse(OLAP)

-- ClickHouse 直读 MySQL 外部表
CREATE TABLE mysql_orders
ENGINE = MySQL('mysql-host:3306', 'mydb', 'orders', 'user', 'password');
 
-- 定期同步增量数据
INSERT INTO orders_local
SELECT * FROM mysql_orders
WHERE updated_at >= yesterday();

或通过 CDC 工具(Canal、Debezium)消费 MySQL binlog 实时同步至 ClickHouse。


Spring Boot 集成

<dependency>
    <groupId>com.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.6.0</version>
</dependency>
spring:
  datasource:
    url: jdbc:clickhouse://localhost:8123/mydb
    username: default
    password: ""
    driver-class-name: com.clickhouse.jdbc.ClickHouseDriver

相关集成:


适用场景

场景推荐
用户行为分析、埋点统计ClickHouse ✅
实时大屏、监控看板ClickHouse ✅
日志存储与分析ClickHouse ✅
业务订单、用户数据(OLTP)MySQL / PostgreSQL
全文搜索Elasticsearch
向量相似度搜索Milvus

相关

  • MySQL — OLTP 场景,与 ClickHouse 互补
  • PostgreSQL — 兼顾分析的关系型数据库
  • Elasticsearch — 日志分析的另一选择
  • Redis — 缓存层,减轻查询压力