全部笔记All notes

ClickHouse 列式数据库教程

阅读 12m 57s12m 57s read

概述

ClickHouse 是一个开源的列式数据库管理系统(DBMS),专为在线分析处理(OLAP)场景设计。它具有极高的查询性能,支持实时数据分析,广泛应用于日志分析、用户行为分析、金融数据分析等场景。

本教程将介绍如何使用 Docker 在 Mac 上快速搭建并运行 ClickHouse,包括基础操作、高级特性和最佳实践。

快速开始

使用 Docker 在 Mac 上快速搭建并运行 ClickHouse,以下是详细教程,包括安装、运行、基本操作以及测试。

1. 安装 Docker

如果你的 Mac 尚未安装 Docker,可以去官网 Docker 官方网站 下载并安装。

2. 拉取并运行 ClickHouse 容器

执行以下命令拉取并运行 ClickHouse:

docker run -d --name clickhouse-server \
  -p 8123:8123 -p 9000:9000 \
  --ulimit nofile=262144:262144 \
  clickhouse/clickhouse-server
  • -p 8123:8123:HTTP 端口(用于 ClickHouse Web UI 和 HTTP 请求)。
  • -p 9000:9000:TCP 端口(用于 ClickHouse 客户端或 JDBC 连接)。
  • --ulimit nofile=262144:262144:调整文件句柄数,防止 ClickHouse 因打开文件数受限导致问题。

3. 进入 ClickHouse 容器

docker exec -it clickhouse-server clickhouse-client

如果成功,会进入 ClickHouse 交互式命令行界面:

ClickHouse client version ...
Connecting to localhost:9000 as user default.
Connected to ClickHouse server version ...

4. 创建数据库

在 ClickHouse 客户端中运行:

CREATE DATABASE test_db;

查看数据库:

SHOW DATABASES;

5. 创建测试表

创建一个用户信息表:

USE test_db;

CREATE TABLE users (
    id UInt32,
    name String,
    age UInt8,
    created_at DateTime
) ENGINE = MergeTree()
ORDER BY id;

查看表:

SHOW TABLES;

6. 插入测试数据

INSERT INTO users VALUES (1, 'Alice', 25, now()), (2, 'Bob', 30, now());

7. 查询数据

SELECT * FROM users;

示例输出:

┌─id─┬─name──┬─age─┬──────created_at─┐
│  1 │ Alice │  25 │ 2024-02-05 12:34:56 │
│  2 │ Bob   │  30 │ 2024-02-05 12:34:57 │
└────┴───────┴─────┴────────────────┘

8. 通过 HTTP API 访问 ClickHouse

ClickHouse 也可以通过 HTTP 进行访问,使用 curl 进行测试:

curl 'http://localhost:8123/' -d 'SELECT * FROM test_db.users FORMAT TabSeparated'

如果输出的是表中的数据,说明 HTTP 访问 ClickHouse 成功。

9. 停止 & 删除 ClickHouse 容器

如果不再需要 ClickHouse 容器,可以执行:

docker stop clickhouse-server
docker rm clickhouse-server

核心功能

本节介绍 ClickHouse 的核心功能,包括数据库管理、表操作、数据查询等基础操作。通过这些操作,你将快速掌握 ClickHouse 的使用方法。

高级特性

以下是更高级的 ClickHouse 操作教程,包括数据导入导出、索引优化、分布式架构、复制机制等高级功能。

1. 高级 ClickHouse 配置

1.1 自定义 ClickHouse 配置文件

如果你需要修改 ClickHouse 的默认参数,可以挂载 config.xml:

docker run -d --name clickhouse-server \
  -p 8123:8123 -p 9000:9000 \
  -v ~/clickhouse-config.xml:/etc/clickhouse-server/config.xml \
  clickhouse/clickhouse-server

clickhouse-config.xml 示例:

<clickhouse>
    <listen_host>0.0.0.0</listen_host>
    <max_connections>1024</max_connections>
    <default_profile>default</default_profile>
    <users>
        <default>
            <password>mysecret</password>
            <networks>
                <ip>::/0</ip>
            </networks>
        </default>
    </users>
</clickhouse>

🚀 这样可以修改 最大连接数、用户密码等配置。

2. 数据导入与导出

2.1 CSV 文件导入

创建测试表:

CREATE TABLE test_csv (
    id UInt32,
    name String,
    age UInt8
) ENGINE = MergeTree()
ORDER BY id;

假设有一个 data.csv:

1,Alice,25
2,Bob,30
3,Charlie,35

使用 clickhouse-client 命令行导入:

cat data.csv | docker exec -i clickhouse-server clickhouse-client --query="INSERT INTO test_csv FORMAT CSV"

2.2 JSON 文件导入

如果 data.json 如下:

{"id":1,"name":"Alice","age":25}
{"id":2,"name":"Bob","age":30}

导入 JSON:

cat data.json | docker exec -i clickhouse-server clickhouse-client --query="INSERT INTO test_csv FORMAT JSONEachRow"

2.3 导出数据

将数据导出到 CSV:

docker exec -i clickhouse-server clickhouse-client --query="SELECT * FROM test_csv FORMAT CSV" > export.csv

3. 分区表优化

ClickHouse 支持 分区表,可以提升查询性能。

3.1 创建按日期分区的表

CREATE TABLE logs (
    id UInt32,
    event_time DateTime,
    message String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time)
ORDER BY event_time;
  • PARTITION BY toYYYYMM(event_time): 按 年月分区
  • ORDER BY event_time: 按 时间排序

插入数据:

INSERT INTO logs VALUES (1, '2024-02-01 12:00:00', 'Event A'), 
                        (2, '2024-02-02 14:00:00', 'Event B');

查询某个分区:

SELECT * FROM logs WHERE event_time >= '2024-02-01' AND event_time < '2024-03-01';

4. ClickHouse 索引优化

ClickHouse 主要有两种索引:

  1. 主键索引(PRIMARY KEY):仅在 MergeTree 及其变体中可用。
  2. 稀疏索引(SKIP INDEXES):用于跳过不必要的查询数据,提高查询性能。

4.1 使用主键索引

CREATE TABLE user_events (
    user_id UInt32,
    event_type String,
    event_time DateTime
) ENGINE = MergeTree()
ORDER BY (user_id, event_time);

这样查询 user_id=123 时更快:

SELECT * FROM user_events WHERE user_id = 123;

4.2 使用跳跃索引

适用于 高基数列(如 UUID):

CREATE TABLE large_table (
    id UInt32,
    uuid String,
    created_at DateTime,
    INDEX uuid_idx uuid TYPE bloom_filter(0.01) GRANULARITY 64
) ENGINE = MergeTree()
ORDER BY id;
  • bloom_filter(0.01): 误判率 1%
  • GRANULARITY 64: 64 行一个索引块

5. ClickHouse 分布式架构

5.1 创建分布式表

ClickHouse 可以搭建 分布式集群,实现 跨服务器查询。

假设有两台服务器:

  • 192.168.1.101
  • 192.168.1.102

在两台服务器上分别创建本地表:

CREATE TABLE local_table ON CLUSTER my_cluster (
    id UInt32,
    name String
) ENGINE = MergeTree()
ORDER BY id;

然后创建 分布式表:

CREATE TABLE dist_table ON CLUSTER my_cluster
(
    id UInt32,
    name String
) ENGINE = Distributed(my_cluster, default, local_table, rand());

这样查询 dist_table 时,会自动分发到两台服务器。

6. ClickHouse 数据复制(Replication)

6.1 配置 ClickHouse 复制

假设有两台服务器:

  • 192.168.1.101(主)
  • 192.168.1.102(从)

主节点:

CREATE TABLE replicated_table (
    id UInt32,
    name String
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/replicated_table', '{replica}')
ORDER BY id;

从节点:

CREATE TABLE replicated_table (
    id UInt32,
    name String
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/replicated_table', '{replica}')
ORDER BY id;

然后测试:

INSERT INTO replicated_table VALUES (1, 'Alice');
SELECT * FROM replicated_table;

从节点也会同步数据。

表引擎深度解析

ClickHouse 的表引擎是其核心特性之一,不同的表引擎决定了数据的存储方式、查询性能、数据复制等特性。本节深入解析 ClickHouse 的主要表引擎。

MergeTree 家族

MergeTree 是 ClickHouse 最重要的表引擎家族,提供了索引、分区、数据采样等高级功能。

1. MergeTree 基础

CREATE TABLE events (
    event_date Date,
    event_id UInt64,
    user_id UInt32,
    event_type String,
    event_data String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id, event_id)
SETTINGS index_granularity = 8192;

核心概念:

  • 分区(Partition):数据的逻辑分组,用于数据管理和查询优化
  • 主键(ORDER BY):决定数据在分区内的物理排序
  • 索引粒度(index_granularity):稀疏索引的行数间隔

2. ReplacingMergeTree

用于自动去重,保留具有相同排序键的最新行:

CREATE TABLE user_profiles (
    user_id UInt32,
    name String,
    email String,
    updated_at DateTime
) ENGINE = ReplacingMergeTree(updated_at)
ORDER BY user_id;

特点:

  • 基于排序键去重
  • 可指定版本列(最大值保留)
  • 去重发生在后台合并时

3. SummingMergeTree

自动对数值列进行求和聚合:

CREATE TABLE user_stats (
    user_id UInt32,
    date Date,
    visits UInt32,
    page_views UInt32,
    duration UInt32
) ENGINE = SummingMergeTree()
ORDER BY (user_id, date);

应用场景:

  • 预聚合数据
  • 实时统计计算
  • 减少存储空间

4. AggregatingMergeTree

支持预定义的聚合函数状态存储:

CREATE TABLE visits_agg (
    date Date,
    user_id UInt32,
    visits AggregateFunction(count),
    total_duration AggregateFunction(sum, UInt32),
    avg_duration AggregateFunction(avg, UInt32)
) ENGINE = AggregatingMergeTree()
ORDER BY (date, user_id);

物化视图结合使用:

CREATE MATERIALIZED VIEW visits_agg_mv
TO visits_agg AS
SELECT
    date,
    user_id,
    countState() AS visits,
    sumState(duration) AS total_duration,
    avgState(duration) AS avg_duration
FROM raw_visits
GROUP BY date, user_id;

5. CollapsingMergeTree

支持行级别的增删操作:

CREATE TABLE user_actions (
    user_id UInt32,
    action String,
    timestamp DateTime,
    sign Int8  -- 1: 添加, -1: 删除
) ENGINE = CollapsingMergeTree(sign)
ORDER BY (user_id, timestamp);

使用示例:

-- 添加记录
INSERT INTO user_actions VALUES (1, 'login', now(), 1);
-- 删除记录(通过插入相反符号的行)
INSERT INTO user_actions VALUES (1, 'login', now(), -1);

6. VersionedCollapsingMergeTree

带版本控制的折叠树:

CREATE TABLE versioned_data (
    id UInt32,
    version UInt32,
    name String,
    sign Int8
) ENGINE = VersionedCollapsingMergeTree(sign, version)
ORDER BY id;

日志引擎家族

适用于小表或临时数据存储。

1. Log

最简单的表引擎,追加写入:

CREATE TABLE simple_log (
    id UInt32,
    message String
) ENGINE = Log;

特点:

  • 不支持索引
  • 不支持并发访问
  • 适合小数据量

2. StripeLog

带标记的日志引擎,支持并发读取:

CREATE TABLE stripe_log_table (
    timestamp DateTime,
    message String
) ENGINE = StripeLog;

3. TinyLog

最小化的日志引擎:

CREATE TABLE tiny_log_table (
    id UInt32,
    data String
) ENGINE = TinyLog;

集成引擎

用于与外部系统集成。

1. MySQL 引擎

直接查询 MySQL 数据:

CREATE TABLE mysql_table (
    id UInt32,
    name String
) ENGINE = MySQL('host:port', 'database', 'table', 'user', 'password');

2. HDFS 引擎

读写 HDFS 文件:

CREATE TABLE hdfs_table (
    id UInt32,
    data String
) ENGINE = HDFS('hdfs://namenode:9000/path/to/file', 'TSV');

3. Kafka 引擎

消费 Kafka 数据流:

CREATE TABLE kafka_queue (
    timestamp UInt64,
    level String,
    message String
) ENGINE = Kafka
SETTINGS 
    kafka_broker_list = 'localhost:9092',
    kafka_topic_list = 'logs',
    kafka_group_name = 'clickhouse-consumer',
    kafka_format = 'JSONEachRow';

创建物化视图消费数据:

CREATE MATERIALIZED VIEW kafka_consumer TO destination_table AS
SELECT 
    timestamp,
    level,
    message
FROM kafka_queue;

特殊用途引擎

1. Distributed

分布式查询引擎:

CREATE TABLE distributed_table AS local_table
ENGINE = Distributed(
    cluster_name,      -- 集群名称
    database_name,     -- 数据库名
    table_name,        -- 本地表名
    sharding_key       -- 分片键(可选)
);

分片策略:

-- 随机分片
ENGINE = Distributed(cluster, db, table, rand());

-- 基于用户ID分片
ENGINE = Distributed(cluster, db, table, user_id);

-- 使用哈希分片
ENGINE = Distributed(cluster, db, table, cityHash64(user_id));

2. Dictionary

字典表引擎:

CREATE DICTIONARY users_dict (
    user_id UInt64,
    username String,
    email String
)
PRIMARY KEY user_id
SOURCE(MYSQL(
    host 'localhost'
    port 3306
    user 'root'
    password 'password'
    db 'users'
    table 'users'
))
LIFETIME(MIN 300 MAX 600)
LAYOUT(HASHED());

3. Memory

内存表引擎:

CREATE TABLE memory_table (
    id UInt32,
    data String
) ENGINE = Memory;

特点:

  • 数据存储在内存中
  • 重启后数据丢失
  • 极高的读写性能
  • 适合临时数据

4. Buffer

缓冲表引擎:

CREATE TABLE buffer_table AS destination_table
ENGINE = Buffer(
    database,           -- 目标数据库
    table,              -- 目标表
    num_layers,         -- 缓冲区数量
    min_time,           -- 最小刷新时间
    max_time,           -- 最大刷新时间
    min_rows,           -- 最小行数
    max_rows,           -- 最大行数
    min_bytes,          -- 最小字节数
    max_bytes           -- 最大字节数
);

表引擎选择决策树

数据特征分析
├─ 需要事务支持?
│  └─ 否 → ClickHouse 不适合
├─ 数据量大小?
│  ├─ < 1GB → TinyLog/Memory
│  ├─ 1GB-100GB → MergeTree
│  └─ > 100GB → 分布式 MergeTree
├─ 更新频率?
│  ├─ 只追加 → MergeTree
│  ├─ 需要更新 → ReplacingMergeTree
│  └─ 需要删除 → CollapsingMergeTree
├─ 是否需要去重?
│  └─ 是 → ReplacingMergeTree
├─ 是否需要预聚合?
│  ├─ 简单求和 → SummingMergeTree
│  └─ 复杂聚合 → AggregatingMergeTree
└─ 外部数据源?
   ├─ MySQL → MySQL引擎
   ├─ Kafka → Kafka引擎
   └─ HDFS → HDFS引擎

性能对比

表引擎写入性能查询性能存储效率适用场景
MergeTree★★★★☆★★★★★★★★★☆通用OLAP
ReplacingMergeTree★★★☆☆★★★★☆★★★★★去重数据
SummingMergeTree★★★☆☆★★★★★★★★★★预聚合
CollapsingMergeTree★★★☆☆★★★☆☆★★★☆☆频繁更新
Log★★★★★★☆☆☆☆★★☆☆☆临时数据
Memory★★★★★★★★★★★☆☆☆☆热数据缓存
Distributed★★★★☆★★★★★-分布式查询

最佳实践建议

  1. MergeTree 优化

    -- 合理设置分区
    PARTITION BY toYYYYMM(date)  -- 按月分区
    
    -- 优化排序键
    ORDER BY (low_cardinality_column, high_cardinality_column)
    
    -- 设置合适的索引粒度
    SETTINGS index_granularity = 8192  -- 默认值
  2. ReplacingMergeTree 使用

    -- 强制执行去重
    OPTIMIZE TABLE table_name FINAL;
    
    -- 查询时确保去重
    SELECT * FROM table_name FINAL;
  3. 分布式表设计

    -- 创建本地表
    CREATE TABLE local_table ON CLUSTER cluster_name (...) 
    ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/table_name', '{replica}');
    
    -- 创建分布式表
    CREATE TABLE dist_table ON CLUSTER cluster_name AS local_table
    ENGINE = Distributed(cluster_name, database, local_table, rand());
  4. Kafka 集成模式

    -- Kafka → 物化视图 → MergeTree
    CREATE MATERIALIZED VIEW mv_from_kafka TO final_table AS
    SELECT 
        toDateTime(timestamp) as event_time,
        JSONExtractString(message, 'user_id') as user_id,
        JSONExtractString(message, 'event') as event
    FROM kafka_table;

性能优化

7. ClickHouse 物化视图(Materialized Views)

ClickHouse 支持 物化视图,用于自动预计算数据,加速查询。

7.1 创建物化视图

CREATE MATERIALIZED VIEW user_summary
ENGINE = AggregatingMergeTree() ORDER BY user_id
AS SELECT user_id, COUNT(*) AS event_count
FROM user_events
GROUP BY user_id;

这样,每次 user_events 变化时,user_summary 也会自动更新。

查询:

SELECT * FROM user_summary;

8. ClickHouse 并行查询优化

ClickHouse 可以 并行查询多个分区,提高查询速度。

8.1 启用并行查询

SET max_threads = 8;

这样 ClickHouse 会用 8 个 CPU 线程 并行查询,提高查询速度。

9. ClickHouse 监控与性能分析

9.1 查看当前运行的查询

SELECT * FROM system.processes;

9.2 查询性能分析

EXPLAIN SELECT * FROM users WHERE age > 30;

9.3 统计表的存储

SELECT table, total_bytes, total_rows FROM system.parts WHERE database = 'default';

最佳实践

架构设计建议

  1. 表设计原则

    • 选择合适的分区键,通常按日期分区
    • 合理设置主键,考虑查询模式
    • 使用适当的数据类型,避免过度设计
  2. 性能优化策略

    • 充分利用分区裁剪
    • 使用物化视图预计算聚合数据
    • 合理设置 max_threads 参数
    • 定期执行 OPTIMIZE TABLE 操作
  3. 数据管理

    • 设置合理的 TTL(生存时间)策略
    • 定期备份重要数据
    • 监控磁盘使用情况
  4. 集群部署

    • 至少 3 个节点保证高可用
    • 使用 ZooKeeper 管理元数据
    • 合理设置副本数量

使用场景

  1. 日志分析

    • Web 访问日志分析
    • 应用程序日志聚合
    • 安全日志审计
  2. 用户行为分析

    • 点击流分析
    • 用户画像构建
    • 漏斗分析
  3. 时序数据

    • IoT 设备数据存储
    • 监控指标存储
    • 金融时序数据
  4. 实时报表

    • 业务数据实时大屏
    • 运营数据分析
    • 财务报表生成

常见问题

Q1: ClickHouse 与传统数据库的区别?

答:ClickHouse 是列式存储,专为 OLAP 场景优化:

  • 列式存储压缩率高,查询特定列更快
  • 支持向量化执行,CPU 利用率高
  • 不支持事务,专注于查询性能
  • 更新操作代价高,适合批量写入

Q2: 如何选择合适的表引擎?

答:常用表引擎选择:

  • MergeTree:最常用,支持索引、分区
  • ReplicatedMergeTree:支持数据复制
  • Distributed:分布式查询
  • Memory:内存表,重启后数据丢失
  • Log:简单日志表,不支持索引

Q3: ClickHouse 内存占用过高怎么办?

答:优化策略:

  1. 调整 max_memory_usage 参数
  2. 减少 max_threads 数量
  3. 使用 SAMPLE 子句进行采样查询
  4. 优化查询,避免全表扫描
  5. 增加服务器内存或使用分布式

Q4: 如何处理数据倾斜?

答:解决方案:

  1. 选择均匀分布的分片键
  2. 使用 rand() 函数随机分片
  3. 预处理数据,保证分布均匀
  4. 监控各节点数据量,及时调整

Q5: ClickHouse 备份恢复策略?

答:推荐方案:

  1. 使用 clickhouse-backup 工具
  2. 定期执行 ALTER TABLE FREEZE 命令
  3. 复制 /var/lib/clickhouse/shadow 目录
  4. 使用复制表自动备份
  5. 导出重要数据到对象存储

总结

在本教程中,你学习了 ClickHouse 的完整使用流程:

  1. 基础入门:Docker 部署、基本操作
  2. 核心功能:数据库、表、查询操作
  3. 高级特性:分区、索引、分布式
  4. 性能优化:物化视图、并行查询
  5. 最佳实践:架构设计、使用场景

这样,你就可以在生产环境中高效使用 ClickHouse 了!

生产实践清单

  • 分区与主键:按业务时间分区(如 toYYYYMM),主键选择高选择性列
  • TTL 生命周期:为明细表配置 TTL,自动转冷存或清理历史
  • 复制与高可用:使用 ReplicatedMergeTree + ZooKeeper 实现多副本
  • 批量写入:控制单批行数(10万级),避免小分片与合并风暴
  • 资源控制:设置 max_memory_usage、max_threads,限制重查询
  • 物化视图:预聚合热维度,降低查询延迟与资源消耗
  • 监控告警:关注 system.metrics、system.parts 与合并队列

查询优化案例

-- 典型明细表:按月分区,按时间+用户排序
CREATE TABLE events (
  ts DateTime,
  user_id UInt64,
  event String,
  payload String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (ts, user_id);

-- 推荐:范围查询 + 等值条件,可利用排序键前缀
SELECT count()
FROM events
WHERE ts >= now() - INTERVAL 1 DAY
  AND ts <  now()
  AND user_id = 12345;

-- 反例:避免函数包裹索引列导致跳过索引
-- 不推荐:WHERE toDate(ts) = '2025-09-19'
-- 推荐:WHERE ts >= '2025-09-19 00:00:00' AND ts < '2025-09-20 00:00:00'

相关文章

数据库技术对比

大数据技术栈

Docker容器化部署

性能监控与优化

开发集成