ClickHouse 表引擎详解

ClickHouse 提供了丰富的表引擎,每种引擎针对特定场景优化。本文详解 MergeTree 家族各引擎的行为差异、适用场景和选择策略。

1. 表引擎概述

ClickHouse 的表引擎(Table Engine)定义了数据如何存储、读取和写入。选择合适的引擎是 ClickHouse 设计的核心决策之一。

引擎分为几大类别:

  • MergeTree 家族:生产环境的主力,支持数据按主键排序、分区、复制
  • Log 家族:轻量级,适合快速写入小表或临时数据
  • 集成引擎:连接外部系统(Kafka、MySQL、PostgreSQL、S3 等)
  • 特殊引擎:内存表、分布式表、视图等

2. MergeTree 引擎家族

MergeTree 是 ClickHouse 最核心和最常用的引擎。所有 MergeTree 变体共享相同的底层存储机制(有序、分区、稀疏索引),但在数据合并时有不同的行为。

2.1 MergeTree(基础引擎)

CREATE TABLE events (
    event_time DateTime,
    user_id UInt64,
    event_type String,
    value Float64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, user_id);

特点:

  • 数据按 ORDER BY 列排序存储
  • 支持数据分区(PARTITION BY)
  • 后台异步合并小 part
  • 不支持去重或聚合——纯存储引擎

适用场景:原始数据存储,不需要去重或自动聚合的场景。

2.2 ReplacingMergeTree(去重)

当需要保证同一主键只有最新一条记录时,使用 ReplacingMergeTree:

CREATE TABLE user_latest_state (
    user_id UInt64,
    status String,
    last_login DateTime,
    score UInt32
) ENGINE = ReplacingMergeTree(last_login)
ORDER BY user_id;

INSERT INTO user_latest_state VALUES
    (1, 'active', '2024-01-01 10:00:00', 100),
    (1, 'inactive', '2024-01-02 11:00:00', 200),
    (2, 'active', '2024-01-01 09:00:00', 50);

-- 合并后,user_id=1 只保留 last_login 最新的记录

关键理解:去重发生在后台合并时,不是写入时。所以刚插入后立刻查询仍可能看到重复:

-- 合并前
SELECT * FROM user_latest_state WHERE user_id = 1;
-- 可能返回 2 条(未合并)

-- 强制最终一致性查询
SELECT * FROM user_latest_state FINAL WHERE user_id = 1;
-- 只返回最新的一条(强制合并)

适用场景:用户状态表(只关心最新状态)、配置表、最近一笔交易记录。

2.3 SummingMergeTree(自动求和)

自动对同一主键的数值列进行求和:

CREATE TABLE event_counters (
    event_date Date,
    event_type String,
    page_url String,
    view_count UInt64,
    click_count UInt64
) ENGINE = SummingMergeTree()
ORDER BY (event_date, event_type, page_url);

-- 插入两条相同主键的记录
INSERT INTO event_counters VALUES ('2024-01-01', 'page_view', '/home', 10, 2);
INSERT INTO event_counters VALUES ('2024-01-01', 'page_view', '/home', 20, 3);

-- 合并后:view_count = 30, click_count = 5

适用场景:计数器、统计报表的预聚合层。

2.4 AggregatingMergeTree(聚合状态)

与 SummingMergeTree 不同,AggregatingMergeTree 可以处理任意聚合函数(uniq、min、max、groupArray 等)。

CREATE TABLE user_metrics (
    event_date Date,
    user_id UInt64,
    total_views AggregateFunction(sum, UInt64),
    unique_pages AggregateFunction(uniq, String),
    max_session_duration AggregateFunction(max, UInt32)
) ENGINE = AggregatingMergeTree()
ORDER BY (event_date, user_id);

-- 插入需要使用 -State 后缀
INSERT INTO user_metrics SELECT
    today(),
    user_id,
    sumState(toUInt64(1)) AS total_views,
    uniqState(page_url) AS unique_pages,
    maxState(session_duration) AS max_session_duration
FROM raw_events
GROUP BY user_id;

-- 查询时使用 -Merge 后缀
SELECT
    event_date,
    sumMerge(total_views) AS views,
    uniqMerge(unique_pages) AS pages,
    maxMerge(max_session_duration) AS max_duration
FROM user_metrics
GROUP BY event_date;

适用场景:需要多维度预聚合的实时指标系统。

2.5 CollapsingMergeTree(状态变更追踪)

通过符号位记录状态变更:

CREATE TABLE account_changes (
    user_id UInt64,
    balance Float64,
    change_time DateTime,
    sign Int8  -- +1 表示新增,-1 表示删除/反转
) ENGINE = CollapsingMergeTree(sign)
ORDER BY (user_id, change_time);

-- 记录状态变化
INSERT INTO account_changes VALUES (1, 100.0, '2024-01-01 10:00:00', 1);
INSERT INTO account_changes VALUES (1, 100.0, '2024-01-01 10:00:00', -1);  -- 撤销
INSERT INTO account_changes VALUES (1, 150.0, '2024-01-01 11:00:00', 1);  -- 新状态

-- 合并后:第一条和第二条抵消,只保留第三条

适用场景:需要追踪状态变更历史的场景,如账户余额变动、库存变化。

2.6 VersionedCollapsingMergeTree

CollapsingMergeTree 的增强版,支持版本号控制:

CREATE TABLE orders (
    order_id UInt64,
    amount Float64,
    status String,
    update_time DateTime,
    sign Int8,
    version UInt32  -- 版本号
) ENGINE = VersionedCollapsingMergeTree(sign, version)
ORDER BY order_id;

-- 同一版本号和符号的记录会相互抵消

2.7 GraphiteMergeTree

用于 Graphite 数据降采样(downsampling),按时间序列自动聚合数据点:

CREATE TABLE metrics (
    path String,
    time DateTime,
    value Float64,
    timestamp UInt32
) ENGINE = GraphiteMergeTree('graphite_rollup')
ORDER BY (path, time);

3. 复制引擎(Replicated*)

所有 MergeTree 引擎都有对应的 Replicated 版本,用于分布式高可用:

-- ReplicatedMergeTree
CREATE TABLE events_local (
    event_time DateTime,
    user_id UInt64
) ENGINE = ReplicatedMergeTree(
    '/clickhouse/tables/{shard}/events',  -- ZooKeeper 路径
    '{replica}'                             -- 副本标识
)
ORDER BY (event_time, user_id);

复制引擎特性:

  • 数据自动同步到所有副本
  • 查询可以在任意副本执行,自动负载均衡
  • 副本故障时自动切换
  • 需要 ZooKeeper(或 ClickHouse Keeper)协调

4. 特殊引擎

4.1 内存引擎

-- Memory:纯内存存储,重启后数据丢失
CREATE TABLE temp_results (
    id UInt64,
    value Float64
) ENGINE = Memory;

-- Buffer:在内存中缓冲,定期刷写到目标表
CREATE TABLE events_buffer AS events
ENGINE = Buffer(default, events, 16, 10, 100, 10000, 1000000, 10000000, 100000000);

4.2 视图引擎

-- 物化视图(前文已详细介绍)
CREATE MATERIALIZED VIEW event_stats_mv ...

-- 普通视图(查询时展开,不存储数据)
CREATE VIEW recent_events AS
SELECT * FROM events WHERE event_time > now() - INTERVAL 7 DAY;

4.3 分布式引擎

-- 分布式表本身不存储数据,将查询路由到分片
CREATE TABLE events_distributed AS events_local
ENGINE = Distributed(
    my_cluster,        -- 集群名
    default,           -- 数据库名
    events_local,      -- 本地表名
    rand()             -- 分片键
);

4.4 集成引擎

-- Kafka 引擎:消费 Kafka 消息
CREATE TABLE kafka_queue (
    user_id UInt64,
    event_type String
) ENGINE = Kafka()
SETTINGS
    kafka_broker_list = 'kafka:9092',
    kafka_topic_list = 'events',
    kafka_group_name = 'clickhouse_consumer',
    kafka_format = 'JSONEachRow';

-- MySQL 引擎:查询 MySQL 表
CREATE TABLE mysql_users (
    id UInt64,
    name String
) ENGINE = MySQL('mysql-host:3306', 'mydb', 'users', 'user', 'password');

-- S3 引擎:查询 S3 上的文件
SELECT * FROM s3(
    'https://bucket.s3.amazonaws.com/data/*.csv',
    'CSV',
    'id UInt64, name String'
);

-- URL 引擎:查询 HTTP 接口
SELECT * FROM url('https://api.example.com/data', JSONEachRow, 'id UInt64');

-- File 引擎:查询本地文件
SELECT * FROM file('data.csv', CSV, 'id UInt64, name String');

5. 引擎选择指南

场景推荐引擎说明
原始事件存储MergeTree最通用,无特殊合并逻辑
用户状态(最新)ReplacingMergeTree自动保留最新记录
计数器/统计SummingMergeTree自动求和数值列
复杂聚合指标AggregatingMergeTree支持任意聚合函数
状态变更追踪CollapsingMergeTree符号位处理状态反转
实时写入缓冲Buffer内存缓冲后刷写
数据降采样GraphiteMergeTree时序数据自动聚合
Kafka 消费Kafka消息队列直接入库
跨分片查询Distributed分布式代理层
外部数据查询MySQL/S3/URL/File联邦查询

6. 表引擎的关键配置

CREATE TABLE events (
    event_time DateTime CODEC(Delta, LZ4),
    -- CODEC 定义列的压缩算法
    -- Delta: 差值编码,适合有序数据
    -- LZ4: 快速压缩算法
    -- ZSTD: 更高压缩率但需要更多 CPU

    user_id UInt64 CODEC(T64, LZ4),
    -- T64: 位压缩,适合整数

    event_type LowCardinality(String),
    -- LowCardinality: 字典编码,适合重复值多的列

    description String CODEC(ZSTD(3))
    -- ZSTD(3): 使用 ZSTD 压缩,级别为 3
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, user_id)
SETTINGS
    index_granularity = 8192,        -- 每多少行一个索引标记
    merge_with_ttl_timeout = 86400,  -- TTL 合并超时(秒)
    storage_policy = 'hot_cold';     -- 存储策略(分层存储)

7. 总结

ClickHouse 的表引擎生态为不同场景提供了精确的工具:

  • MergeTree 家族是核心:根据数据更新模式选择合适的变体
  • 了解合并时序:所有 MergeTree 变体的特殊行为都发生在后台合并时
  • 复制需要协调服务:Replicated 引擎需要 ZooKeeper 或 Keeper
  • 集成引擎拓展边界:可以在不导入数据的情况下查询外部系统

引擎选择直接影响查询性能和数据一致性。在设计阶段投入时间选择正确的引擎,可以避免后期的重大重构。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「数据库」更多文章

  1. ClickHouse 监控与运维
  2. ClickHouse 生产案例与最佳实践
  3. ClickHouse 架构与设计原理