Article

列式数据库ClickHouse

更新于:2026-07-16

第 1 章:环境准备与安装

1.1 支持的操作系统与依赖

概念名称说明注意事项
支持的操作系统官方主要支持 Linux 发行版,包括 Ubuntu(20.04+)、Debian(10+)、CentOS/RHEL(7+)、AlmaLinux、Rocky Linux 等。macOS 和 Windows 仅通过 Docker 或 WSL2 支持。不建议在生产环境使用 macOS/Windows 原生部署。
CPU 架构x86_64(主流),ARM64(实验性支持,从 v22.3 起)ARM64 需确认版本兼容性,部分功能可能受限。
依赖库glibc ≥ 2.19、libssl、libunwind、zlib、lz4、zstd 等大多数现代 Linux 发行版默认满足;源码编译需手动安装开发包。
文件系统推荐 XFS 或 ext4避免使用网络文件系统(如 NFS)存储数据目录,影响性能和稳定性。
内存与磁盘最低 2GB RAM,建议 8GB+;SSD 强烈推荐ClickHouse 对 I/O 敏感,HDD 可能导致查询延迟高。

1.2 安装方式(APT/YUM/Docker/源码)

安装方式操作细节注意事项
APT(Ubuntu/Debian)(见代码示例 1)官方推荐使用 Altinity 或 Yandex 官方源;避免使用过旧的系统仓库版本。
YUM(CentOS/RHEL)(见代码示例 2)RHEL/CentOS 7 需启用 EPEL;确保 SELinux 不阻止服务启动。
Docker(见代码示例 3)默认用户无密码;生产环境应挂载配置文件并设置用户权限。
源码编译(见代码示例 4)编译耗时长(30min~数小时),需至少 16GB 内存;仅建议开发者或定制需求使用。

代码示例 1:APT 安装

# 1. 添加官方仓库
curl -fsS https://packagecloud.io/install/repositories/Altinity/stable/script.deb.sh | sudo bash

# 2. 安装
sudo apt install clickhouse-server clickhouse-client

代码示例 2:YUM 安装

# 1. 创建 repo 文件
sudo tee /etc/yum.repos.d/clickhouse.repo <<EOF
[clickhouse]
name=clickhouse
baseurl=https://packages.clickhouse.com/rpm/stable
gpgcheck=0
enabled=1
EOF

# 2. 安装
sudo yum install clickhouse-server clickhouse-client

代码示例 3:Docker 安装

# 1. 拉取镜像
docker pull clickhouse/clickhouse-server

# 2. 启动容器
docker run -d --name ch-server \
  -p 9000:9000 -p 8123:8123 \
  -v $(pwd)/ch_data:/var/lib/clickhouse \
  clickhouse/clickhouse-server

代码示例 4:源码编译

# 1. 安装依赖(CMake、Ninja、GCC 10+ 等)

# 2. 克隆仓库
git clone --recursive https://github.com/ClickHouse/ClickHouse.git

# 3. 编译
cd ClickHouse && mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
ninja clickhouse

1.3 启动与验证服务

操作名称操作细节注意事项
启动服务(systemd)sudo systemctl start clickhouse-server首次启动会自动生成默认配置和数据目录(/var/lib/clickhouse)。
设置开机自启sudo systemctl enable clickhouse-server生产环境建议启用。
查看服务状态sudo systemctl status clickhouse-server若失败,检查 /var/log/clickhouse-server/ 下的日志文件。
验证本地连接(见代码示例 5)默认无需密码;若配置了 users.xml 中的 default 用户密码,则需加 --password
验证 HTTP 接口curl 'http://localhost:8123/'(返回 “Ok.” 表示成功)8123 是 HTTP 接口端口,用于 JDBC、Grafana 等集成。
检查监听端口(见代码示例 6)确保 9000(TCP Native)、8123(HTTP)、9009(副本通信)等端口正常监听。
停止服务sudo systemctl stop clickhouse-server强制 kill 可能导致后台合并任务中断,建议用 systemctl

代码示例 5:验证本地连接

clickhouse-client
# 或
clickhouse-client --host 127.0.0.1 --port 9000

代码示例 6:检查监听端口

ss -tulnp | grep clickhouse
# 或
netstat -tulnp | grep :9000

第 2 章:命令行客户端操作

2.1 连接 ClickHouse 服务

方法/命令语法用途代码示例注意事项
本地连接(默认用户)clickhouse-client使用 default 用户连接本地服务clickhouse-client假设服务运行在 127.0.0.1:9000,且 default 用户无密码。
指定主机与端口clickhouse-client --host --port连接远程或非默认端口实例clickhouse-client --host 192.168.1.10 --port 9000端口 9000 是 native TCP 协议端口。
指定用户名和密码clickhouse-client --user --password使用认证用户登录clickhouse-client --user admin --password mypass密码明文可见,建议在安全环境中使用;可省略 --password 让客户端交互式输入。
使用配置文件连接clickhouse-client --config-file从配置文件读取连接参数clickhouse-client --config-file /etc/clickhouse-client/config.xml配置文件需包含 userpasswordhost 等节点。
安全连接(TLS)clickhouse-client --secure启用 TLS 加密连接clickhouse-client --host ch.example.com --secure需服务器配置 SSL 证书,端口通常为 9440。

2.2 基本交互命令(退出、帮助、历史)

命令/操作语法/操作方式用途代码示例注意事项
退出客户端CTRL+D 或输入 EXIT; / QUIT;退出交互式会话EXIT;不区分大小写;分号可省略但建议保留。
查看帮助输入 HELP;显示内置帮助信息HELP;仅提供基本语法提示,非完整文档。
查看历史命令使用上下箭头键浏览历史 SQL 命令(交互操作)历史保存在 ~/.clickhouse-client-history 文件中。
清屏CTRL+L清除终端屏幕(快捷键)依赖终端支持,非 ClickHouse 特有功能。
多行输入直接换行输入编写多行 SQL(见代码示例 7)客户端自动识别语句结束(以分号或空行+分号判断)。

代码示例 7:多行输入

SELECT
    count(*)
FROM table;

2.3 执行 SQL 脚本文件

方法/命令语法用途代码示例注意事项
执行本地脚本clickhouse-client --query "$(cat file.sql)"执行单个 SQL 文件clickhouse-client --query "$(cat init.sql)"适用于单语句或多语句脚本;注意 shell 对特殊字符的转义。
使用 -m / --multiline 模式clickhouse-client -m < file.sql支持多行语句(推荐方式)clickhouse-client --multiline < queries.sql自动处理分号分隔的多条语句;更可靠。
指定数据库执行clickhouse-client --database < file.sql在指定数据库上下文中执行clickhouse-client --database analytics < load.sql避免在脚本中硬编码 USE database
结合变量替换clickhouse-client --param_name=value --query "SELECT {name:String}"使用参数化查询(见代码示例 8)需在查询中使用 {name:Type} 占位符,支持 String、Identifier、Int 等类型。

代码示例 8:变量替换

clickhouse-client --param_table=logs --query "SELECT count() FROM {table:Identifier}"

2.4 格式化输出选项

输出格式语法用途代码示例注意事项
TabSeparated(默认)--format TabSeparated列之间用制表符分隔clickhouse-client --query="SELECT 1,2" --format TabSeparated可被 Excel 或 awk 直接处理。
CSV--format CSV输出 CSV 格式clickhouse-client --query="SELECT 'a',1" --format CSV字符串自动加引号,支持 --output_format_csv_delimiter 自定义分隔符。
JSON--format JSON返回结构化 JSONclickhouse-client --query="SELECT 1 AS x" --format JSON包含 meta、data、rows 等字段,适合程序解析。
JSONEachRow--format JSONEachRow每行一个 JSON 对象clickhouse-client --query="SELECT number FROM numbers(2)" --format JSONEachRow流式处理友好,常用于日志导出。
PrettyCompact--format PrettyCompact美观表格(默认交互模式)clickhouse-client --query="SELECT 1" --format PrettyCompact适合人工阅读;非交互模式下需显式指定。
TSVWithNames--format TSVWithNamesTSV + 列名头clickhouse-client --query="SELECT 1 AS id" --format TSVWithNames便于导入到 pandas 等工具。
Null--format Null不输出结果(仅执行)clickhouse-client --query="INSERT ..." --format Null用于性能测试或静默插入。
指定输出文件--output将结果写入文件clickhouse-client --query="SELECT ..." --format CSV --output result.csv等价于重定向 >,但更可控。

第 3 章:数据库与表管理(DDL)

3.1 创建/删除数据库

方法/语句语法用途代码示例注意事项
创建数据库CREATE DATABASE [IF NOT EXISTS] db_name [ENGINE = engine]创建新数据库CREATE DATABASE IF NOT EXISTS analytics;默认使用 Ordinary 引擎;分布式场景可选 Lazy、Atomic 等。
指定数据库引擎CREATE DATABASE db_name ENGINE = Atomic使用支持原子操作的引擎CREATE DATABASE logs ENGINE = Atomic;Atomic 引擎支持原子 DROP/RENAME,推荐 v20.10+ 使用。
删除数据库DROP DATABASE [IF EXISTS] db_name删除整个数据库及其中所有表DROP DATABASE IF EXISTS test_db;操作不可逆;需确保无活跃查询或依赖。
查看所有数据库SHOW DATABASES列出当前用户可见的数据库SHOW DATABASES;受权限控制,非所有库都可见。
使用数据库USE db_name设置当前会话默认数据库USE analytics;后续表操作无需加前缀 db.table

3.2 表引擎介绍(MergeTree 系列等)

引擎名称说明适用场景注意事项
MergeTree最基础的列式存储引擎,支持主键排序、分区、后台合并通用 OLAP 场景,日志、事件分析必须指定 ORDER BY;不支持更新/删除(逻辑上)。
ReplacingMergeTree在合并时删除具有相同主键的重复行(保留最新)需去重但允许短暂重复的场景”去重”仅在合并后生效,非实时;需配合 FINAL 或业务层处理。
SummingMergeTree合并时对指定列自动求和聚合指标预计算(如 PV/UV 累计)非聚合列取任意值;需谨慎设计主键。
AggregatingMergeTree存储预聚合状态(如 AggregateFunction 类型)高性能物化视图底层存储需配合 -State/-Merge 函数使用。
ReplicatedMergeTree带副本的 MergeTree,基于 ZooKeeper 协调高可用生产环境表名需全局唯一;依赖 ZooKeeper 集群。
Distributed分布式引擎,代理查询到多个节点构建集群透明查询入口本身不存数据;需先有本地表。
Memory数据全在内存,服务重启丢失临时表、测试、小维表不支持索引,性能极高但容量有限。
File / URL / Kafka外部数据源引擎直接查询外部文件或流通常用于 ETL 中间步骤,非持久存储。

注:所有 *MergeTree 引擎均继承自 MergeTree,核心能力一致。

3.3 创建/修改/删除表

操作语法用途代码示例注意事项
创建表CREATE TABLE [IF NOT EXISTS] table_name (col1 Type, ...) ENGINE = engine [PARTITION BY expr] [ORDER BY expr] [PRIMARY KEY expr] [TTL expr]定义结构化表(见代码示例 9)ORDER BY 是必须的;PARTITION BY 可选但影响性能。
从查询创建表CREATE TABLE table_name ENGINE = ... AS SELECT ...基于查询结果建表并导入数据(见代码示例 10)表结构由 SELECT 推断。
修改表(添加列)ALTER TABLE table_name ADD COLUMN col_name Type [AFTER other_col]扩展表结构ALTER TABLE events ADD COLUMN device String AFTER user_id;支持在线操作,不影响读写。
修改列类型ALTER TABLE table_name MODIFY COLUMN col_name NewType更改列数据类型ALTER TABLE events MODIFY COLUMN event LowCardinality(String);需兼容原数据,否则失败。
删除列ALTER TABLE table_name DROP COLUMN col_name移除列ALTER TABLE events DROP COLUMN unused_flag;不可逆;数据立即不可见。
重命名表RENAME TABLE old_name TO new_name表重命名RENAME TABLE logs TO event_logs;Atomic 引擎下为原子操作。
删除表DROP TABLE [IF EXISTS] table_name删除表及其数据DROP TABLE IF EXISTS temp_table;分布式表需先删本地表再删 Distributed 表。
清空表数据TRUNCATE TABLE table_name删除所有数据但保留结构TRUNCATE TABLE staging;比 DELETE 快得多;对 MergeTree 系列有效。

代码示例 9:创建表

CREATE TABLE events (
    ts DateTime,
    user_id UInt32,
    event String
) ENGINE = MergeTree()
ORDER BY (user_id, ts);

代码示例 10:从查询创建表

CREATE TABLE user_summary
ENGINE = SummingMergeTree
ORDER BY user_id AS
SELECT user_id, sum(clicks)
FROM raw
GROUP BY user_id;

3.4 分区与 TTL 管理

操作/概念语法/说明用途代码示例注意事项
分区定义PARTITION BY toYYYYMM(date)按表达式划分数据块(见代码示例 11)分区键应低基数(如天、月),避免过多小分区。
查看分区SELECT partition, name, rows FROM system.parts WHERE table = 'hits';监控分区状态SELECT partition FROM system.parts WHERE table = 'logs' AND active;active = 1 表示当前有效分区。
手动删除分区ALTER TABLE table_name DROP PARTITION '202501';删除整月数据ALTER TABLE logs DROP PARTITION '202412';立即释放磁盘空间;不可恢复。
分区附加ALTER TABLE table_name ATTACH PARTITION '202501' FROM src_table;从另一表导入分区ALTER TABLE prod ATTACH PARTITION '202501' FROM staging;要求表结构和分区键完全一致。
TTL 定义(行级)TTL ts + INTERVAL 30 DAY自动过期旧数据(见代码示例 12)后台合并时清理;非实时。
TTL 定义(列级)TTL ts + INTERVAL 1 DAY TO DISK 'slow'列降冷存储(见代码示例 13)需配置多磁盘策略(storage.xml)。
修改 TTLALTER TABLE table MODIFY TTL ts + INTERVAL 60 DAY;更新过期策略ALTER TABLE logs MODIFY TTL event_date + INTERVAL 90 DAY;对全表生效,但清理仍依赖合并。
强制触发 TTL 合并OPTIMIZE TABLE table FINAL;立即执行 TTL 清理OPTIMIZE TABLE sessions FINAL;消耗大量 I/O 和 CPU,慎用。

代码示例 11:分区定义

CREATE TABLE hits (...)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY ...;

代码示例 12:行级 TTL

CREATE TABLE sessions (...)
ENGINE = MergeTree()
ORDER BY id
TTL created_at + INTERVAL 7 DAY;

代码示例 13:列级 TTL 降冷

CREATE TABLE metrics (...)
ENGINE = MergeTree()
ORDER BY ...
TTL event_time + INTERVAL 30 DAY TO DISK 'hdd';

第 4 章:数据操作(DML)

4.1 插入数据(INSERT)

方法/语句语法用途代码示例注意事项
基本插入INSERT INTO table VALUES (val1, val2, ...)插入单行或多行数据INSERT INTO events (ts, user_id, event) VALUES ('2025-01-01 10:00:00', 1001, 'click');列名可省略(需按建表顺序);支持多行 VALUES。
从 SELECT 插入INSERT INTO table SELECT ... FROM other_table批量导入或 ETL(见代码示例 14)源与目标列数和类型需兼容。
使用 FORMAT 插入echo "data" | clickhouse-client --query="INSERT INTO table FORMAT CSV"通过命令行批量导入结构化数据cat data.csv | clickhouse-client --query="INSERT INTO logs FORMAT CSV";支持 TSV、JSONEachRow、Parquet 等格式。
参数化插入clickhouse-client --param_data="1,'a'" --query="INSERT INTO t VALUES ({data})"安全传参(防注入)(见代码示例 15)需使用 {name:Type} 占位符。
异步插入(Buffer 表)向 Buffer 引擎表插入,后台刷入主表缓解高频小写入压力INSERT INTO events_buffer VALUES (...);Buffer 表有内存限制,可能丢数据,仅用于临时缓冲。

⚠️ ClickHouse 不支持 INSERT IGNOREON DUPLICATE KEY UPDATE

代码示例 14:从 SELECT 插入

INSERT INTO daily_summary
SELECT
    toDate(ts),
    user_id,
    count()
FROM events
GROUP BY toDate(ts), user_id;

代码示例 15:参数化插入

clickhouse-client --param_uid=123 --param_evt='login' \
  --query="INSERT INTO events VALUES (now(), {uid:UInt32}, {evt:String});"

4.2 更新与删除(受限操作)

操作语法用途代码示例注意事项
删除数据(ALTER DELETE)ALTER TABLE table DELETE WHERE condition逻辑删除满足条件的行ALTER TABLE events DELETE WHERE ts < '2024-01-01';异步执行,非实时生效;需 MergeTree 系列引擎;消耗大量资源。
更新数据(ALTER UPDATE)ALTER TABLE table UPDATE col = expr WHERE condition修改列值ALTER TABLE users UPDATE status = 'inactive' WHERE last_login < today() - 30;同样异步;不能更新主键或分区键。
查看 mutation 状态SELECT * FROM system.mutations WHERE table = 'events';监控 DELETE/UPDATE 进度SELECT mutation_id, is_done, latest_failed_part FROM system.mutations WHERE table = 'logs';is_done=1 表示完成;失败需手动处理。
取消 mutationKILL MUTATION WHERE mutation_id = '...';终止正在执行的变更KILL MUTATION WHERE table = 'events' AND is_done = 0;仅取消调度,已修改的数据不可逆。
替代方案:重建表(见代码示例 16)高效”删除”大量数据(见代码示例 16)适用于删除比例大(>30%)的场景,更高效。

⚠️ UPDATE/DELETE 是重量级操作,应尽量避免;推荐使用 ReplacingMergeTree + 插入新状态或应用层覆盖。

代码示例 16:重建表替代方案

CREATE TABLE events_clean AS
SELECT * FROM events WHERE ts >= '2024-01-01';

RENAME TABLE events TO events_old, events_clean TO events;

4.3 查询数据(SELECT 基础)

功能语法用途代码示例注意事项
基本查询SELECT expr [, ...] FROM table [WHERE ...] [LIMIT n]获取数据SELECT user_id, count() FROM events WHERE ts >= today() GROUP BY user_id LIMIT 10;默认无 ORDER BY,结果顺序不确定。
投影列SELECT *, col AS alias选择全部或重命名列SELECT *, length(event) AS evt_len FROM events;* 包含所有列,慎用于宽表。
条件过滤WHERE condition行筛选SELECT * FROM events WHERE user_id IN (1001, 1002) AND event = 'purchase';支持函数、子查询、IN、LIKE 等。
排序ORDER BY expr [ASC|DESC]控制输出顺序SELECT * FROM events ORDER BY ts DESC LIMIT 100;
分页LIMIT n [OFFSET m]分批获取结果SELECT * FROM events ORDER BY ts LIMIT 1000 OFFSET 2000;OFFSET 效率低,建议用游标(WHERE ts > last_seen_ts)。
聚合GROUP BY cols WITH TOTALS分组统计SELECT country, sum(amount) FROM sales GROUP BY country WITH TOTALS;WITH TOTALS 添加汇总行;支持 ROLLUP/CUBE(实验性)。
采样SAMPLE n快速近似分析SELECT count() FROM events SAMPLE 0.1;表需建表时指定 SAMPLE BY;仅部分引擎支持。

4.4 使用 WITH、子查询、JOIN

功能语法用途代码示例注意事项
CTE(WITH 子句)WITH expr AS alias SELECT ...定义中间变量或子查询WITH today() AS now SELECT count() FROM events WHERE ts >= now - INTERVAL 7 DAY;支持多表达式:WITH a AS (1), b AS (2) SELECT a + b;
标量子查询SELECT (SELECT max(ts) FROM events) AS last_event返回单值(见代码示例 17)必须返回一行一列;性能较差,慎用。
派生表(FROM 子查询)SELECT * FROM (SELECT user_id, count() AS c FROM events GROUP BY user_id) WHERE c > 10;嵌套聚合(见代码示例 18)子查询必须有别名。
INNER JOINSELECT a.*, b.name FROM table_a a INNER JOIN table_b b ON a.id = b.id关联维表SELECT e.*, u.country FROM events e INNER JOIN users u ON e.user_id = u.id;小表(< 数百万行)放右表;建议使用 Hashed/Join 引擎优化。
LEFT JOINSELECT ... FROM a LEFT JOIN b ON ...保留左表全部行SELECT e.ts, u.email FROM events e LEFT JOIN users u ON e.user_id = u.id;未匹配字段为 NULL。
使用 Join 引擎优化CREATE TABLE dict ENGINE = Join(ANY, LEFT, id) AS SELECT id, name FROM users;预加载维表到内存(见代码示例 19)更高效替代普通 JOIN;适用于静态维表。
GLOBAL JOINSELECT ... FROM remote_table GLOBAL IN (SELECT ...)分布式 JOIN 优化SELECT * FROM cluster_table GLOBAL ALL INNER JOIN local_dict ON ...;避免数据倾斜;将小表广播到各节点。

⚠️ JOIN 默认使用 hash_map,右表不宜过大;超大表关联建议改用字典(Dictionary)或应用层处理。

代码示例 17:标量子查询

SELECT
    user_id,
    (SELECT name FROM users WHERE id = events.user_id)
FROM events
LIMIT 10;

代码示例 18:派生表

SELECT *
FROM (
    SELECT city, avg(salary) AS avg_sal
    FROM employees
    GROUP BY city
) WHERE avg_sal > 5000;

代码示例 19:Join 引擎优化

SELECT e.*, dictGet('dict', 'name', e.user_id)
FROM events e;

第 5 章:数据导入与导出

5.1 从 CSV/TSV/TBL 文件导入

操作方式语法 / 命令用途代码示例注意事项
使用 INSERT + FORMAT CSVcat file.csv | clickhouse-client --query="INSERT INTO table FORMAT CSV"从标准输入导入 CSVcat data.csv | clickhouse-client --query="INSERT INTO logs FORMAT CSV";默认字段用逗号分隔,字符串需双引号;空值为 \N 或留空。
指定 CSV 分隔符--format_csv_delimiter=','自定义 CSV 分隔符clickhouse-client --format_csv_delimiter=';' --query="INSERT INTO t FORMAT CSV" < data_semi.csv通过客户端参数设置,非 SQL 语句。
导入 TSV(制表符分隔)... FORMAT TabSeparated导入 TSV 格式cat data.tsv | clickhouse-client --query="INSERT INTO t FORMAT TabSeparated";支持 TabSeparatedWithNames(含列名头)。
导入 TBL(竖线分隔)... FORMAT TSKV 或自定义TBL 非标准格式,通常指 | 分隔(自定义处理)
跳过 CSV 头部使用 TSVWithNames 或预处理忽略第一行列名tail -n +2 data.csv | clickhouse-client --query="INSERT INTO t FORMAT CSV";若使用 FORMAT CSVWithNames,则自动跳过首行并校验列名。
处理转义与引号默认兼容 RFC 4180正确解析带逗号的字符串"id","msg"\n1,"Hello, world!" → 正确导入确保源文件符合标准;否则需清洗。

5.2 导出为不同格式(JSONEachRow、Parquet 等)

输出格式语法用途代码示例注意事项
JSONEachRowSELECT ... FORMAT JSONEachRow每行一个 JSON 对象,流式友好clickhouse-client --query="SELECT * FROM events LIMIT 10 FORMAT JSONEachRow" > out.json字段名作为 key,适合日志管道(如 Filebeat)。
CSVSELECT ... FORMAT CSV标准 CSV 输出clickhouse-client --query="SELECT user_id, ts FROM events FORMAT CSV" > data.csv字符串自动加双引号;可用 --output_format_csv_delimiter 改分隔符。
CSVWithNamesSELECT ... FORMAT CSVWithNamesCSV + 列名头clickhouse-client --query="SELECT * FROM t FORMAT CSVWithNames" > data.csv便于 pandas.read_csv() 直接读取。
ParquetSELECT ... FORMAT Parquet列式二进制格式,高效存储clickhouse-client --query="SELECT * FROM large_table FORMAT Parquet" > data.parquet需 v21.1+;支持嵌套类型;可被 Spark/DuckDB 直接读取。
ArrowSELECT ... FORMAT Arrow内存分析格式clickhouse-client --query="SELECT * FROM t FORMAT Arrow" > data.arrow适用于 Python(pyarrow)、R 等生态。
TSVWithNamesAndTypesSELECT ... FORMAT TSVWithNamesAndTypesTSV + 列名 + 类型clickhouse-client --query="SELECT 1 AS x FORMAT TSVWithNamesAndTypes"用于调试或元数据保留。
指定输出文件--output file 或重定向保存结果到文件clickhouse-client --query="SELECT ..." --format Parquet --output data.parquet--output 更可靠,避免 shell 编码问题。

5.3 使用 clickhouse-client 批量导入

方法语法 / 命令用途代码示例注意事项
流式导入大文件cat big.csv | clickhouse-client --query="INSERT INTO t FORMAT CSV"高效导入 GB 级数据pv data.csv | clickhouse-client --query="INSERT INTO logs FORMAT CSV";避免一次性加载到内存;pv 可显示进度。
分块导入(避免超时)(见代码示例 20)控制单次插入规模(见代码示例 20)单次 INSERT 建议 ≤ 100 万行;过大可能 OOM 或超时。
使用压缩流导入zcat data.csv.gz | clickhouse-client --query="INSERT INTO t FORMAT CSV"直接导入压缩文件zcat logs-2025.csv.gz | clickhouse-client --query="INSERT INTO raw_logs FORMAT CSV";节省磁盘和 I/O;支持 gzip、xz 等。
指定数据库和用户--database db --user u --password p明确连接上下文clickhouse-client --database analytics --user loader --password xxx --query="INSERT ..."避免依赖默认配置。
错误容忍(跳过坏行)无内置选项,需预处理ClickHouse 不支持”跳过错误行”使用 awk/sed 过滤非法行后再导入建议先用小样本测试格式兼容性。

💡 最佳实践:单次 INSERT 数据量控制在 10MB~100MB(原始文本),对应约 10⁵~10⁶ 行。

代码示例 20:分块导入

split -l 100000 data.csv chunk_
for f in chunk_*; do
    cat $f | clickhouse-client --query="INSERT INTO t FORMAT CSV"
done

5.4 使用 clickhouse-local 处理本地文件

功能语法 / 命令用途代码示例注意事项
本地查询 CSV 文件clickhouse-local --file data.csv --input-format CSV --structure 'id UInt32, name String' --query "SELECT count() FROM table"无需服务,直接分析文件(见代码示例 21)table 是固定表名;结构需显式声明。
转换格式(CSV → Parquet)clickhouse-local --file in.csv --input-format CSV --structure '...' --query "SELECT * FROM table" --output-format Parquet > out.parquet文件格式转换(见代码示例 22)无需启动 server,纯本地计算。
过滤并导出子集... --query "SELECT * FROM table WHERE condition"清洗数据(见代码示例 23)支持完整 SQL(函数、聚合等)。
多文件 JOIN需先加载为临时表(不直接支持多文件)间接实现(见代码示例 24)使用 file() 表函数(v22.3+ 支持)。
性能优势向量化执行,C++ 引擎快速处理 GB 级文件在笔记本上 1 秒处理 1000 万行 CSV比 Python/pandas 快 5~10 倍;适合 ETL 脚本。

⚠️ clickhouse-localclickhouse-server 共享同一二进制,但独立运行,不依赖服务进程。

代码示例 21:本地查询 CSV

clickhouse-local \
  --file logs.csv \
  --input-format CSV \
  --structure 'ts DateTime, uid UInt32' \
  --query "SELECT toDate(ts), count() FROM table GROUP BY 1"

代码示例 22:格式转换

clickhouse-local \
  --file data.csv \
  --structure 'x Int64' \
  --input-format CSV \
  --output-format Parquet \
  --query "SELECT * FROM table" > data.parquet

代码示例 23:过滤导出

clickhouse-local \
  --file raw.csv \
  --structure 'a String, b Int32' \
  --input-format CSV \
  --query "SELECT a FROM table WHERE b > 100" \
  --output-format TSV > clean.tsv

代码示例 24:多文件 JOIN

clickhouse-local --query "
SELECT *
FROM file('a.csv', CSV, 'x Int32') AS a
ALL INNER JOIN file('b.csv', CSV, 'x Int32, y String') AS b
USING(x)
"

---

## 第 6 章:系统监控与管理

### 6.1 查看系统表(`system.*`)

| 系统表名称 | 说明 | 常用查询示例 | 注意事项 |
|------------|------|-------------|----------|
| `system.tables` | 当前数据库中所有表的元信息 | `SELECT name, engine, total_rows FROM system.tables WHERE database = 'analytics';` | `total_rows` 是估算值(MergeTree 表);实时行数需查 `system.parts` |
| `system.databases` | 所有数据库信息 | `SELECT name, engine FROM system.databases;` | 显示引擎类型(如 Atomic、Ordinary)。 |
| `system.columns` | 表的列定义 | `SELECT table, name, type FROM system.columns WHERE table = 'events';` | 可用于生成 DDL 或校验 schema。 |
| `system.parts` | 数据分区物理文件信息 | `SELECT table, partition, rows, bytes_on_disk, active FROM system.parts WHERE database = currentDatabase();` | `active=1` 表示当前有效分区;合并后旧 parts 标记为 inactive。 |
| `system.merges` | 正在进行的后台合并任务 | `SELECT table, elapsed, progress FROM system.merges;` | 监控大合并是否阻塞写入。 |
| `system.replicas` | 副本状态(ReplicatedMergeTree) | `SELECT table, is_leader, queue_size, last_queue_update FROM system.replicas;` | `queue_size > 0` 可能表示同步延迟。 |
| `system.metrics` | 实时指标(连接数、内存等) | `SELECT metric, value FROM system.metrics WHERE metric LIKE '%Query%';` | 指标每秒更新;适合做监控采集。 |
| `system.asynchronous_metrics` | 异步指标(磁盘、线程池等) | `SELECT * FROM system.asynchronous_metrics WHERE metric = 'OSUserTime';` | 更新频率较低(几秒一次)。 |
| `system.settings` | 当前会话或全局设置 | `SELECT name, value FROM system.settings WHERE changed;` | `changed=1` 表示非默认值。 |

> 💡 所有 `system.*` 表均为只读,且对权限敏感(需相应 GRANT)。

### 6.2 监控查询性能(query_log、processes)

| 功能/表 | 语法 / 查询 | 用途 | 代码示例 | 注意事项 |
|----------|------------|------|----------|----------|
| 查看当前运行查询 | `SELECT query_id, user, query, elapsed FROM system.processes;` | 实时监控活跃查询 | `SELECT query_id, elapsed, read_rows FROM system.processes WHERE elapsed > 10;` | 可识别慢查询;kill 前需记录 `query_id` |
| 查询历史日志(query_log) | `SELECT event_time, query, query_duration_ms, read_rows FROM system.query_log WHERE event_date = today() AND type = 2;` | 分析已完成查询性能 | `SELECT user, avg(query_duration_ms) FROM system.query_log WHERE event_time >= now() - INTERVAL 1 HOUR GROUP BY user;` | 需启用 `<query_log>`(默认开启);`type=2` 表示完成事件。 |
| 异常查询检测 | `... WHERE exception <> ''` | 查找失败查询 | `SELECT query, exception FROM system.query_log WHERE exception != '' ORDER BY event_time DESC LIMIT 10;` | 有助于排查 SQL 错误或资源不足问题。 |
| 内存与 I/O 使用 | `SELECT memory_usage, peak_memory_usage, read_bytes FROM system.query_log WHERE query_id = '...';` | 诊断资源消耗 | `SELECT memory_usage, peak_memory_usage, read_bytes FROM system.query_log WHERE query_id = '...';` | `memory_usage` 单位为字节;可设 `max_memory_usage` 限制。 |
| 启用 query_log | 默认已启用(配置见 `config.xml` | 确保日志持久化 | (见注意事项) | 日志保留天数由 `<query_log><database>system</database><table>query_log</table><partition_by>toYYYYMM(event_date)</partition_by></query_log>` 控制;若未记录,检查 `<logger><level>trace</level></logger>` 和磁盘空间。 |

> ⚠️ `system.query_log` 默认保留 3~7 天(取决于 TTL),生产环境建议导出到专用分析表。

### 6.3 杀死正在运行的查询

| 操作 | 语法 | 用途 | 代码示例 | 注意事项 |
|------|------|------|----------|----------|
| 终止指定查询 | `KILL QUERY WHERE query_id = '...' [SYNC]` | 取消单个查询 | `KILL QUERY WHERE query_id = '12345-abc';` | `SYNC` 表示等待取消完成(默认异步)。 |
| 终止某用户所有查询 | `KILL QUERY WHERE user = 'loader';` | 批量终止 | `KILL QUERY WHERE user = 'etl_user';` | 谨慎使用,可能影响业务。 |
| 终止长时间查询 | `KILL QUERY WHERE elapsed > 300;` | 自动清理慢查询 | (可集成到监控脚本) | 建议先记录再 kill,便于审计。 |
| 查看 kill 结果 | `SELECT * FROM system.processes WHERE query_id = '...';` | 验证是否已终止 | 若返回空,则已成功取消 | 已开始写入的结果可能部分提交(非事务性)。 |
| 权限要求 | 需要 `KILL QUERY` 权限 | 安全控制 | `GRANT KILL QUERY ON *.* TO admin;` | 普通用户无法 kill 他人查询。 |

> 💡 `KILL QUERY` 不保证立即生效,但通常在毫秒级响应;不会中断后台合并或 mutation。

### 6.4 配置文件与参数调整(config.xml、users.xml)

| 配置项 | 文件路径 | 用途 | 示例配置片段 | 注意事项 |
|--------|----------|------|-------------|----------|
| 主配置文件 | `/etc/clickhouse-server/config.xml` | 服务核心参数 | (见代码示例 25) | 修改后需重启服务(部分支持 reload)。 |
| 用户与权限 | `/etc/clickhouse-server/users.xml` | 定义用户、密码、配额 | (见代码示例 26) | 支持 include 分离:`<users incl="users.xml" />` |
| 自定义 profile | `users.xml` `<profiles>` | 设置资源限制 | (见代码示例 27) | 可通过 `SET PROFILE poweruser` 切换。 |
| 动态参数设置 | `SET max_threads = 8;` | 会话级调整 | `SET max_block_size = 65536;` | 仅影响当前连接;优先级高于 `config.xml` |
| 启用配置热加载 | `config.xml` `<configuration>` + INCLUDE 机制 | 避免重启 | `<remote_servers incl="clickhouse_remote_servers" />` | 大部分网络、用户配置支持 reload;存储路径等需重启。 |
| 日志级别 | `config.xml` `<logger>` | 控制日志输出 | `<logger><level>warning</level><log>/var/log/clickhouse-server/clickhouse-server.log</log></logger>` | 生产环境建议 warning error,避免日志爆炸。 |
| 自定义宏(集群) | `config.xml` `<macros>` | 分布式表标识 | `<macros><shard>01</shard><replica>host-a</replica></macros>` | 用于 Replicated 表路径生成,必须唯一。 |

**代码示例 25:主配置文件**

```xml
<listen_host>::</listen_host>
<http_port>8123</http_port>
<path>/var/lib/clickhouse/</path>

代码示例 26:用户与权限

<users>
    <default>
        <password></password>
        <networks>
            <ip>::/0</ip>
        </networks>
        <profile>default</profile>
    </default>
</users>

代码示例 27:自定义 profile

<profiles>
    <readonly>
        <readonly>1</readonly>
    </readonly>
    <poweruser>
        <max_memory_usage>10000000000</max_memory_usage>
    </poweruser>
</profiles>

🔁 重载配置命令(无需重启):

代码示例 28:重载配置

sudo clickhouse reload config
# 或发送信号:
sudo kill -HUP $(cat /var/run/clickhouse-server/clickhouse-server.pid)

第 7 章:高级功能

7.1 物化视图

概念/操作语法 / 说明用途代码示例注意事项
创建物化视图(隐式目标表)CREATE MATERIALIZED VIEW mv ENGINE = AggregatingMergeTree() ORDER BY ... AS SELECT ... FROM src GROUP BY ...自动聚合源表数据(见代码示例 29)ClickHouse 会自动创建内部目标表 _mv;无法直接 INSERT 到 MV。
创建物化视图(显式目标表)CREATE TABLE mv_dst (...); CREATE MATERIALIZED VIEW mv TO mv_dst AS SELECT ...显式控制目标表结构(见代码示例 30)推荐方式,便于管理 TTL、分区等。
数据触发机制仅对新插入到源表的数据生效增量更新INSERT INTO events VALUES (...); → 自动触发 MV 计算不处理历史数据;已有数据需手动补全。
补全历史数据INSERT INTO mv_dst SELECT ... FROM src WHERE ...初始化或修复(见代码示例 31)需确保与 MV 逻辑一致。
删除物化视图DROP TABLE mv_name移除 MV 及其内部表(若隐式)DROP TABLE user_daily_mv;若为显式 TO 表,仅删除 MV 对象,目标表保留。
查看物化视图定义SHOW CREATE TABLE mv_name调试与审计SHOW CREATE TABLE user_daily_mv;显示完整 CREATE 语句。

⚠️ 物化视图是 INSERT 触发的流式计算,非传统数据库的”预计算缓存”;不支持 UPDATE/DELETE 源表时自动修正。

代码示例 29:创建物化视图(隐式目标表)

CREATE MATERIALIZED VIEW user_daily_mv
ENGINE = SummingMergeTree()
ORDER BY (user_id, day) AS
SELECT
    user_id,
    toDate(ts) AS day,
    count() AS cnt
FROM events
GROUP BY user_id, day;

代码示例 30:创建物化视图(显式目标表)

-- 先创建目标表
CREATE TABLE daily_stats (
    day Date,
    total UInt64
) ENGINE = SummingMergeTree
ORDER BY day;

-- 再创建物化视图指向目标表
CREATE MATERIALIZED VIEW mv TO daily_stats AS
SELECT
    toDate(ts) AS day,
    count() AS total
FROM events
GROUP BY toDate(ts);

代码示例 31:补全历史数据

INSERT INTO user_daily_mv
SELECT
    user_id,
    toDate(ts) AS day,
    count()
FROM events
WHERE ts < '2025-01-01'
GROUP BY user_id, toDate(ts);

7.2 字典(Dictionaries)

操作/类型语法 / 配置用途代码示例注意事项
创建字典(XML 配置)/etc/clickhouse-server/dicts.d/user_dict.xml 中定义将外部维表加载到内存(见代码示例 32)需重启或 reload 配置;支持 hashed、cache、complex_key_hashed 等 layout。
创建字典(DDL 方式,v21.8+)CREATE DICTIONARY dict_name (...) PRIMARY KEY id SOURCE(MYSQL(...)) LAYOUT(HASHED()) LIFETIME(300);动态创建,无需 XML(见代码示例 33)推荐新项目使用;支持 ON CLUSTER
查询字典dictGet('dict_name', 'attr', id)在查询中关联维表SELECT event, dictGet('user_dict', 'name', user_id) FROM events;内存查找,性能极高(微秒级)。
刷新字典SYSTEM RELOAD DICTIONARY dict_name手动更新缓存SYSTEM RELOAD DICTIONARY user_dict;自动刷新由 LIFETIME 控制。
查看字典状态SELECT * FROM system.dictionaries;监控加载情况SELECT name, bytes_allocated, loading_status FROM system.dictionaries;loading_status = 'loaded' 表示成功。
缓存字典(cache layout)LAYOUT(CACHE(SIZE_IN_CELLS 1000000))大维表按需缓存CREATE DICTIONARY big_dim (...) LAYOUT(CACHE(SIZE_IN_CELLS 5000000)) ...;适合 ID 稀疏访问的大表;未命中回源。

💡 字典适用于静态或缓慢变化的维表(如用户信息、产品目录),比 JOIN 快 10~100 倍。

代码示例 32:创建字典(XML 配置)

<dictionaries>
    <dictionary>
        <name>user_dict</name>
        <source>
            <mysql>
                <host>localhost</host>
                <port>3306</port>
                <user>u</user>
                <password>p</password>
                <db>app</db>
                <table>users</table>
            </mysql>
        </source>
        <layout><hashed/></layout>
        <structure>
            <id><name>id</name></id>
            <attribute>
                <name>name</name>
                <type>String</type>
            </attribute>
        </structure>
    </dictionary>
</dictionaries>

代码示例 33:创建字典(DDL 方式)

CREATE DICTIONARY user_dict (
    id UInt64,
    name String
)
PRIMARY KEY id
SOURCE(MYSQL(
    HOST 'localhost'
    PORT 3306
    USER 'u'
    PASSWORD 'p'
    DB 'app'
    TABLE 'users'
))
LAYOUT(HASHED())
LIFETIME(MIN 300 MAX 600);

7.3 分布式表与集群配置

概念/操作语法 / 配置用途代码示例注意事项
定义集群(config.xml)<remote_servers> 中配置声明物理节点拓扑(见代码示例 34)需在所有节点保持一致;支持副本和分片。
创建本地表(各节点)CREATE TABLE local_table (...) ENGINE = ReplicatedMergeTree(...)存储实际数据(见代码示例 35){shard}{replica} 来自 macros。
创建分布式表CREATE TABLE dist_table AS local_table ENGINE = Distributed(cluster, db, local_table, rand());透明查询入口CREATE TABLE hits_all AS hits_local ENGINE = Distributed(cluster_2s_2r, default, hits_local, cityHash64(url));分布式表不存数据;分片键建议高基数。
插入数据到分布式表INSERT INTO dist_table VALUES (...);自动路由到分片INSERT INTO hits_all VALUES ('2025-01-01 10:00:00', 'https://example.com');数据先写本地 buffer(可选),再异步转发。
全局子查询SELECT * FROM dist_table WHERE id GLOBAL IN (SELECT id FROM local_dim);避免数据倾斜SELECT * FROM dist_table WHERE id GLOBAL IN (SELECT id FROM local_dim);GLOBAL 将右表广播到所有分片。
监控集群状态SELECT * FROM system.clusters;查看节点列表SELECT cluster, shard_num, replica_num, host_address FROM system.clusters WHERE cluster = 'cluster_2s_2r';验证配置是否生效。
异步写入优化启用 internal_replication避免重复写副本在 Distributed 表 DDL 中设 internal_replication=true(默认)仅写一个副本,由 ReplicatedMergeTree 自动同步。

⚠️ 分布式查询性能高度依赖分片键选择网络延迟;避免跨分片大 JOIN。

代码示例 34:定义集群

<clickhouse_remote_servers>
    <cluster_2s_2r>
        <shard>
            <replica>
                <host>ch1</host>
                <port>9000</port>
            </replica>
            <replica>
                <host>ch2</host>
                <port>9000</port>
            </replica>
        </shard>
        <shard>
            <!-- ... -->
        </shard>
    </cluster_2s_2r>
</clickhouse_remote_servers>

代码示例 35:创建本地表 ON CLUSTER

CREATE TABLE hits_local ON CLUSTER cluster_2s_2r (
    ts DateTime,
    url String
) ENGINE = ReplicatedMergeTree(
    '/clickhouse/tables/{shard}/hits',
    '{replica}'
) ORDER BY ts;

7.4 外部数据源集成(MySQL、Kafka)

集成方式语法 / 配置用途代码示例注意事项
MySQL 表引擎CREATE TABLE mysql_table ENGINE = MySQL('host:port', 'db', 'table', 'user', 'password');直接查询 MySQL 表(见代码示例 36)每次查询都访问 MySQL;不适合高频或大数据量。
Kafka 表引擎CREATE TABLE kafka_queue ENGINE = Kafka() SETTINGS kafka_broker_list = 'kafka:9092', kafka_topic_list = 'logs', kafka_group_name = 'ch_group', kafka_format = 'JSONEachRow';消费 Kafka 流(见代码示例 37)Kafka 表只用于消费,不能 SELECT 直接读(需配合物化视图写入 MergeTree)。
Kafka → MergeTree 流水线物化视图从 Kafka 表插入构建实时管道(见代码示例 38)数据被消费后即从 Kafka 移除(由 group 管理 offset)。
外部数据字典(MySQL)见 7.2 节将 MySQL 表作为字典CREATE DICTIONARY user_dict (...) SOURCE(MYSQL(...)) ...;内存加载,高效查询。
JDBC 表函数(临时查询)SELECT * FROM jdbc('mysql?...', 'SELECT ...');一次性外部查询SELECT * FROM jdbc('mysql://user:pass@host:3306/db', 'SELECT id, name FROM users WHERE active=1');需启用 jdbc-bridge 服务;性能较低。
导出到 MySQLINSERT INTO FUNCTION odbc(...)反向写入INSERT INTO FUNCTION odbc('DSN=my_mysql;Uid=u;Pwd=p', 'db.table') SELECT * FROM clickhouse_table;需配置 ODBC DSN;通常用于小批量导出。

🔌 Kafka 最佳实践:

  • 使用独立的 Kafka 表 + 物化视图
  • 设置合理的 kafka_max_block_size(默认 65536 行)
  • 监控 system.kafka_consumers 查看 lag

代码示例 36:MySQL 表引擎

CREATE TABLE users_mysql
ENGINE = MySQL('mysql:3306', 'app', 'users', 'reader', 'pass');

SELECT * FROM users_mysql LIMIT 10;

代码示例 37:Kafka 表引擎

CREATE TABLE kafka_events
ENGINE = Kafka
SETTINGS
    kafka_broker_list = 'k1:9092',
    kafka_topic_list = 'web_events',
    kafka_group_name = 'clickhouse',
    kafka_format = 'JSONEachRow';

代码示例 38:Kafka → MergeTree 流水线

CREATE MATERIALIZED VIEW ingest_mv TO events_local AS
SELECT * FROM kafka_events;

二、ClickHouse 原理速查文档

此部分聚焦于 ClickHouse 的内部机制与设计思想,适合进阶理解。

第 1 章:架构概览

1.1 单机 vs 分布式架构

概念名称说明适用场景注意事项
单机架构所有数据存储与计算在单一节点完成,使用 MergeTree 等本地表引擎开发测试、中小规模分析(TB 级以下)、快速原型性能受限于单机 CPU/内存/磁盘;无高可用能力。
分布式架构由多个 ClickHouse 节点组成集群,通过 Distributed 表引擎实现透明查询路由大规模数据(PB 级)、高并发、高可用生产环境需额外管理 ZooKeeper(用于副本协调)、网络配置、分片策略。
数据分片(Sharding)将数据水平拆分到多个节点,提升写入吞吐与查询并行度写入量大或单表超大(> 数十 TB)分片键(sharding key)设计至关重要;不均会导致数据倾斜。
副本(Replication)同一分片的数据在多个节点冗余存储,基于 ReplicatedMergeTree + ZooKeeper要求高可用、容忍节点故障副本不提升查询性能(除非显式并行读);增加存储成本。
查询执行模式单机:本地执行;分布式:发起节点(initiator)协调各分片执行并汇总结果分布式查询中,JOIN / GROUP BY 若未按分片键对齐,会触发数据重分布(expensive)。
部署复杂度单机:一键安装;分布式:需配置集群、宏、ZooKeeper、负载均衡等推荐使用 Altinity Kubernetes Operator 或 clickhouse-operator 简化运维。

1.2 列式存储原理

概念名称说明优势注意事项
列式存储(Column-Oriented Storage)每列数据独立连续存储,而非按行组织1. 极高压缩比(同列数据类型一致)
2. 查询仅读取所需列,减少 I/O
3. 向量化处理友好
不适合频繁更新整行或 OLTP 场景。
数据分区(Partition)按表达式(如 toYYYYMM(date))将数据划分为物理目录快速删除整分区(DROP PARTITION);查询可跳过无关分区(partition pruning)分区粒度过细(如按天)会导致小文件过多,影响性能;建议月/周级。
主键索引(Primary Index)基于 ORDER BY 键构建稀疏索引(每 ~8192 行一个标记)快速定位数据块(mark range),非精确查找索引不唯一;主要用于范围过滤和排序加速。
压缩(LZ4/ZSTD)每列按 granule(默认 8192 行)独立压缩典型压缩比 3:1 ~ 10:1;降低磁盘与内存占用可为不同列指定 codec(如 CODEC(ZSTD(3)))。
后台合并(Merge)定期将多个小 parts 合并为大 part,清理重复(ReplacingMergeTree)或应用 TTL减少文件数量,提升查询效率;优化存储布局合并消耗 CPU/IO;可通过 SYSTEM STOP MERGES 临时禁用(调试用)。
数据局部性(Data Locality)相同行范围内所有列数据在磁盘上位置相近减少随机 I/O,提升顺序读性能依赖合理的 ORDER BY 设计(高基数维 + 时间)。

1.3 向量化执行引擎

概念名称说明工作机制注意事项
向量化执行(Vectorized Execution)以”批”为单位(Block,通常数千~数万行)处理数据,而非逐行CPU 利用 SIMD 指令并行计算整列;减少函数调用开销所有内置函数(如 toDatelengthsum)均为向量化实现。
Block(数据块)查询执行的基本单元,包含多列的数组(ColumnVector)每列是连续内存数组(如 UInt32[]),便于缓存友好访问Block 大小受 max_block_size 控制(默认 65536 行)。
延迟物化(Late Materialization)仅在必要时才组合多列或生成最终结果行先在单列上过滤/聚合,最后投影所需字段显著减少中间数据量,尤其在宽表(数百列)场景。
表达式编译优化查询中的表达式(如 WHERE a + b > 10)被编译为高效执行路径避免解释执行;常量折叠、谓词下推自动应用用户无法直接控制,但可通过 EXPLAIN 查看优化效果。
并行处理单机内多线程并行处理不同 data parts 或 blocksmax_threads 控制并发度(默认等于 CPU 核数)过高线程数可能导致上下文切换开销;I/O 密集型查询未必受益。
零拷贝读取从磁盘读取后,数据在内存中以只读 Column 形式传递避免中间复制;减少内存分配依赖 C++ 的 shared_ptr 和 immutable column design。

💡 **性能关键:**ClickHouse 的极致性能 = 列存(减少 I/O)+ 向量化(提升 CPU 利用率)+ 数据局部性(提升缓存命中)。

第 2 章:存储引擎详解

2.1 MergeTree 引擎家族

引擎名称核心机制典型用途注意事项
MergeTree基础列式存储引擎,支持分区、主键排序、后台合并通用日志/事件分析表必须指定 ORDER BY;不支持去重或聚合。
ReplacingMergeTree(version)合并时对相同主键的行保留 version 最大(或最后)的一条需最终一致去重的场景(如用户状态更新)“去重”仅在合并后生效;查询时需用 FINAL 或业务层处理重复。
SummingMergeTree(columns)合并时对指定数值列自动求和,非聚合列取任意值预聚合指标(如 PV、计费量)若未指定 columns,则对所有数值列求和;主键设计决定聚合粒度。
AggregatingMergeTree存储 -State 聚合函数的中间状态(如 uniqStatequantileState高性能物化视图底层存储查询时必须用 -Merge 函数(如 uniqMerge)还原结果。
CollapsingMergeTree(sign)通过 sign 列(+1/-1)标记行的”插入/删除”,合并时抵消支持逻辑删除的流式更新需严格保证成对写入;否则残留脏数据。
VersionedCollapsingMergeTree在 Collapsing 基础上增加版本号,解决乱序问题更健壮的更新/删除模型需同时提供 sign 和 version 列。
Replicated*MergeTree所有上述引擎的副本版本,基于 ZooKeeper 协调生产环境高可用部署表路径必须全局唯一(通常含 {shard} 宏);依赖 ZooKeeper 可用性。

💡 所有 *MergeTree 引擎均继承自 MergeTree,共享分区、索引、合并等核心机制。

2.2 数据分区与主键索引

概念说明配置方式注意事项
分区键(PARTITION BY)将数据按表达式划分为独立物理目录(如按月)PARTITION BY toYYYYMM(event_date)分区数建议 < 1000;过多小分区导致元数据膨胀和查询变慢。
主键(PRIMARY KEY)用于构建稀疏索引,加速过滤和排序PRIMARY KEY (user_id, event_date)(若未指定,默认等于 ORDER BY)主键是 ORDER BY 的前缀;不强制唯一。
排序键(ORDER BY)决定数据在磁盘上的物理排列顺序ORDER BY (event_date, user_id, event_type)高频过滤字段放前;时间字段通常放首位以利 TTL 和范围查询。
稀疏索引(Primary Index)index_granularity 行(默认 8192)记录一个主键标记自动构建,不可显式创建查询时通过二分查找确定 mark 范围,再读取对应 granules。
跳数索引(Skip Index)辅助索引,对特定列构建 minmax/set/bloom_filter 等摘要INDEX idx_name col TYPE minmax GRANULARITY 4需权衡写入开销与查询收益;适合高选择性过滤。
数据局部性相同行范围内所有列在磁盘上连续存储由 ORDER BY 决定良好局部性可使 SSD 顺序读达 GB/s 级吞吐。

⚠️ 修改 ORDER BY 或 PARTITION BY 需重建表;无法通过 ALTER 直接变更。

2.3 合并(Merge)与后台任务

后台任务触发条件作用监控方式注意事项
数据合并(Merge)新 parts 数量达到阈值,或空闲资源充足将多个小 parts 合并为大 part,减少文件数SELECT * FROM system.merges;
SELECT table, count() FROM system.parts WHERE active GROUP BY table;
合并期间原 parts 仍可读;新写入生成新 part,不影响服务。
TTL 删除合并过程中检查行或列的 TTL 条件自动清理过期数据或降冷存储SELECT * FROM system.parts WHERE ttl_delete > 0;不是实时删除;需等待合并触发。
Replication 队列处理副本从 ZooKeeper 获取日志并应用保证副本间数据一致SELECT * FROM system.replicas;(关注 queue_sizelast_queue_update网络中断会导致队列堆积;需监控 lag。
mutations(ALTER DELETE/UPDATE)用户发起 DDL 变更异步执行行级修改SELECT * FROM system.mutations;每个 mutation 触发一次全表合并,成本极高。
复制日志清理ZooKeeper 中旧日志超过保留阈值防止 ZK 节点爆炸zookeeper_session_expirationcleanup_delay_period 控制需确保所有副本已消费日志再清理。
合并策略控制通过 merge_tree 设置调节平衡写入延迟与查询性能<max_bytes_to_merge_at_max_space_in_pool><number_of_free_entries_in_pool_to_lower_max_size_of_merge>默认策略适用于大多数场景;极端负载需调优。

🔧 手动触发合并(调试用):

代码示例 39:手动触发合并

OPTIMIZE TABLE table_name [PARTITION partition_expr] FINAL;

⚠️ FINAL 强制完成所有待合并,消耗大量 I/O,禁止在生产高峰使用。

2.4 副本与 ZooKeeper 集成

概念说明配置要点注意事项
副本表引擎ReplicatedMergeTree(zk_path, replica_name)zk_path 必须全局唯一(如 /clickhouse/tables/{shard}/table);replica_name 通常为 {replica}同一分片的不同副本必须使用相同 zk_path
ZooKeeper 作用存储副本元数据:part 日志、复制队列、leader 选举需独立高可用 ZK 集群(3 或 5 节点)ZK 是单点故障源;不可用时写入阻塞,查询仍可读。
复制流程写入节点将 part 信息写入 ZK;其他副本监听 ZK 并拉取数据数据块通过 HTTP 从副本直接传输(非经 ZK)网络带宽影响同步速度;建议副本同机房。
internal_replicationDistributed 表写入时是否只发一个副本ENGINE = Distributed(..., internal_replication = true)设为 true(默认)可避免重复写入;由 Replicated 表自动同步。
副本修复手动从健康副本克隆数据SYSTEM RESTORE REPLICA table_name;适用于 ZK 元数据损坏但本地数据完好场景。
ZK 路径清理删除不再使用的表后,ZK 路径不会自动清除需手动清理或使用 system.drop_replica残留路径会占用 ZK 内存;定期审计。
替代方案:ClickHouse KeeperClickHouse 自研 ZK 兼容实现(v21.8+)启用 <keeper_server> 配置降低外部依赖;性能与 ZK 相当,推荐新集群使用。

🛡️ 高可用建议:

  • ZooKeeper 集群与 ClickHouse 节点物理隔离
  • 监控 ZK 的 zk_avg_latencyzk_outstanding_requests
  • 副本数 ≥ 2,跨机架/可用区部署

第 3 章:查询处理流程

3.1 查询解析与优化

阶段说明优化行为注意事项
词法与语法分析将 SQL 字符串解析为抽象语法树(AST)报告语法错误(如缺失分号、非法关键字)支持 ClickHouse 扩展语法(如 FINALSAMPLEARRAY JOIN)。
语义分析绑定表名、列名、函数,验证类型兼容性检查列是否存在、函数是否支持、权限是否满足若列名歧义(多表同名),需显式指定表前缀。
常量折叠(Constant Folding)在解析阶段计算常量表达式SELECT 1 + 2 FROM tSELECT 3 FROM t减少运行时计算;适用于所有确定性表达式。
谓词下推(Predicate Pushdown)将 WHERE 条件尽可能下推到数据源层SELECT * FROM (SELECT a,b FROM t) WHERE a > 10 → 直接在 t 上过滤显著减少中间数据量;对子查询、JOIN、物化视图均有效。
标量子查询提升将可复用的标量子查询提取为临时列避免重复执行相同子查询仅适用于无关联的标量子查询。
分区剪枝(Partition Pruning)根据 WHERE 条件跳过无关分区WHERE event_date >= '2025-01-01' → 仅扫描 202501 及之后分区依赖分区键为表达式结果(如 toYYYYMM(event_date))。
主键范围剪枝利用稀疏索引缩小需读取的数据块范围WHERE user_id IN (1001,1002) → 定位到包含这些 ID 的 marks要求过滤字段是 ORDER BY 前缀;否则全表扫描。

💡 用户可通过 EXPLAIN 查看优化效果:

代码示例 40:EXPLAIN 查看优化效果

EXPLAIN PLAN
SELECT count()
FROM events
WHERE ts > now() - INTERVAL 1 DAY;

3.2 执行计划生成

概念说明关键组件注意事项
Pipeline(管道)查询执行的物理计划,由多个处理器(Processor)组成Source → Filter → Aggregating → Sink每个 Processor 处理一个 Block(数据块),支持并行流水线。
执行树(Query Plan)逻辑操作符树(如 Join、Aggregation、ReadFromMergeTree)通过 EXPLAIN PIPELINE 查看不同于传统数据库的”执行计划”,ClickHouse 更强调流式处理。
数据源读取(ReadFromMergeTree)从 MergeTree 表读取数据的算子自动应用分区剪枝、主键剪枝、跳数索引是性能关键路径;可通过 system.partssystem.part_log 监控 I/O。
聚合策略选择根据 GROUP BY 基数自动选择哈希表或排序聚合低基数 → in-memory hash table;高基数 → external merge内存不足时自动溢出到磁盘(需配置 tmp_path)。
JOIN 算法默认使用 Hash Join(右表构建哈希表)支持 ALL/ANY INNER/LEFT JOIN右表应尽量小;大表 JOIN 建议改用字典或预关联。
物化视图重写某些查询可自动路由到物化视图(实验性)需开启 use_materialized_view 设置目前支持有限;通常需显式查询 MV 表。
EXPLAIN 命令查看执行计划细节EXPLAIN [AST | SYNTAX | PLAN | PIPELINE]不同层级揭示不同细节;PIPELINE 最具体。

📊 示例:查看并行 pipeline

代码示例 41:EXPLAIN PIPELINE

EXPLAIN PIPELINE
SELECT count()
FROM events
WHERE ts >= today();

输出将显示 Limit → Aggregating → Expression → Filter → ReadFromMergeTree 及线程数。

3.3 并行与分布式查询

机制说明控制参数注意事项
单机并行(Local Parallelism)多线程并行处理不同 data parts 或 blocksmax_threads(默认 = CPU 核数)
max_block_size(默认 65536 行)
I/O 密集型查询未必受益于高线程数;CPU 密集型(如复杂函数)更明显。
分布式查询发起(Initiator)客户端连接的节点作为协调者无显式参数Initiator 负责汇总结果,可能成为瓶颈(尤其大结果集)。
分片并行执行查询被广播到所有相关分片并行执行由 Distributed 表自动处理各分片独立执行本地计划;网络延迟影响总耗时。
全局子查询(GLOBAL)将右表数据广播到所有分片GLOBAL IN / GLOBAL JOIN避免因分片不均导致 JOIN 错误;但增加网络开销。
数据重分布(Shuffle)当 GROUP BY / JOIN 键与分片键不一致时自动触发性能极差;应尽量使分片键 = 高频 GROUP BY / JOIN 键。
外部聚合(External Aggregation)内存不足时将中间聚合结果写入磁盘max_bytes_before_external_group_by需配置足够 tmp_path 空间;SSD 可缓解性能下降。
流式结果返回查询结果边计算边返回(非全量缓冲)默认行为适合 LIMIT 或前端分页;但无法预知总行数。
查询熔断与超时防止资源耗尽max_execution_time
timeout_overflow_mode = 'break'
超时查询被 kill,返回部分结果或错误(取决于设置)。

🌐 分布式最佳实践:

  • 分片键 = 业务主键(如 cityHash64(user_id)
  • 避免 SELECT *,只取必要列
  • 大结果集用 LIMIT 或导出到文件
  • 监控 system.query_log 中的 profile_events(如 NetworkSendBytes

第 4 章:性能调优原理

4.1 主键与排序键设计

概念说明设计建议注意事项
ORDER BY(排序键)决定数据在磁盘上的物理排列顺序,也是主键基础高频过滤字段 + 时间字段放前,如 (tenant_id, event_date, user_id)一旦建表不可更改;需预判查询模式。
PRIMARY KEYORDER BY 的前缀,用于构建稀疏索引通常省略,等同于 ORDER BY;若仅需索引部分列可显式指定索引列必须是 ORDER BY 的连续前缀。
高基数 vs 低基数高基数列(如 user_id)适合放后;低基数(如 status)适合放前低基数列前置利于压缩和跳数索引高基数列放最前会导致索引粒度变差(每 mark 覆盖值太少)。
时间字段位置通常作为第二或第三列(首列为租户/业务域)(tenant_id, event_date, user_id, event_type)便于 TTL、分区剪枝、时间范围查询。
多维查询优化若常按 A、B、C 组合查询,应将三者都放入 ORDER BY即使不全用作过滤,也能提升局部性宽 ORDER BY 会略微增加合并开销,但收益远大于成本。
稀疏索引效率index_granularity 行(默认 8192)一个索引标记查询条件匹配前缀时,可跳过大量 granules若查询字段不在 ORDER BY 前缀中,则无法使用索引,触发全表扫描。

反例:ORDER BY (event_date, rand()) —— 破坏局部性,严重降低压缩率与查询性能。

4.2 分区策略影响

分区策略说明适用场景注意事项
按天分区(toYYYYMMDD(date)每天一个分区日志系统、需按天快速删除分区数增长快;1 年 = 365 分区,可能超限(建议 < 1000)。
按月分区(toYYYYMM(date)每月一个分区中长期分析、用户行为表平衡管理粒度与分区数量;推荐默认选择。
不分区(PARTITION BY tuple()所有数据在一个分区小表(< 10 GB)或高频写入微批场景合并压力大;删除旧数据需 ALTER DELETE(昂贵)。
按业务维度分区(如 country)按枚举值分区多租户 SaaS、国家隔离仅当维度值少(< 100)且查询总带该条件时有效;否则导致数据倾斜。
分区与 TTL 联动PARTITION BY toYYYYMM(ts) TTL ts + INTERVAL 12 MONTH自动归档/清理DROP PARTITION 比行级 TTL 更高效。
分区过多的后果元数据膨胀、查询规划变慢、ZooKeeper 压力大可通过 system.parts 监控分区数;避免动态高基数字段作分区键。

⚠️ **重要:**分区不是性能优化手段,而是数据生命周期管理工具。性能主要靠 ORDER BY 和索引。

4.3 内存与磁盘 I/O 优化

优化方向配置/方法作用注意事项
调整 max_threadsSET max_threads = 8;users.xml 中 profile控制单查询 CPU 并发默认 = CPU 核数;I/O 密集型可适当降低。
控制内存使用max_memory_usage(单查询)
max_memory_usage_for_all_queries(全局)
防止 OOM建议设为物理内存的 70%~80%。
启用外部聚合max_bytes_before_external_group_by = 5000000000(5GB)内存不足时溢出到磁盘需配置高速 SSD 作为 tmp_path;性能下降约 2~5 倍。
使用 SSD 存储数据目录挂载 NVMe/SSD提升随机读吞吐(关键!)ClickHouse 对 I/O 延迟极度敏感;HDD 查询延迟可能达秒级。
文件系统选择XFS 或 ext4XFS 在大文件和高并发下更稳定避免 btrfs、ZFS(除非明确调优)。
压缩 codec 优化CODEC(LZ4HC(9))CODEC(ZSTD(3))提高压缩比,减少 I/OLZ4 速度快;ZSTD 压缩率高;可按列指定。
调整 index_granularity建表时 SETTINGS index_granularity = 4096更细索引(适合点查)值越小,索引越大,内存占用越高;默认 8192 适合范围查询。
禁用不必要的合并SYSTEM STOP MERGES table;(临时)减少写入高峰期 I/O仅用于紧急调试;长期禁用会导致查询变慢。

💡 监控指标:

  • system.metrics: MemoryTracking, Query
  • system.asynchronous_metrics: DiskDataBytes, OSCPUWaitTime

4.4 延迟物化与谓词下推

机制说明性能收益注意事项
延迟物化(Late Materialization)仅在最终阶段才组合多列生成完整”行”减少中间数据搬运;尤其在宽表(100+ 列)中显著用户无感知;ClickHouse 自动应用。
谓词下推(Predicate Pushdown)将 WHERE 条件尽可能下推到数据源读取层避免读取无关列和行;减少 I/O 与计算对子查询、JOIN、物化视图均有效;可通过 EXPLAIN 验证。
投影下推(Projection Pushdown)仅读取 SELECT 中涉及的列列存天然优势;I/O 与内存占用线性下降SELECT * 会抵消此优势;应避免。
表达式下推计算如 toDate(ts) 在读取时完成避免后续额外计算步骤需列支持向量化函数(内置函数均支持)。
跳数索引辅助过滤对非主键列构建 minmax/bloom_filter 索引加速 WHERE col = value 类查询需权衡写入开销;适合高选择性列。
FINAL 与物化视图替代避免运行时 FINAL,改用 ReplacingMergeTree + 应用层去重FINAL 强制合并,性能极差仅在数据量极小时考虑 FINAL

🔍 验证优化是否生效:

代码示例 42:验证优化

EXPLAIN PLAN
SELECT user_id, sum(clicks)
FROM events
WHERE event_date >= '2025-01-01'
GROUP BY user_id;

观察是否出现 Filter 在 ReadFromMergeTree 之后(错误)还是之前(正确)。

第 5 章:高可用与扩展性

5.1 副本机制(ReplicatedMergeTree)

概念说明配置要点注意事项
副本表定义使用 Replicated*MergeTree(zk_path, replica_name) 引擎zk_path 必须全局唯一(如 /clickhouse/tables/{shard}/events);replica_name 通常为 {replica} 宏(来自 config.xml同一分片的所有副本必须使用相同的 zk_path
数据同步原理写入节点将 part 信息写入 ZooKeeper;其他副本监听并拉取数据数据通过 HTTP 直接从副本节点传输(非经 ZK)网络带宽决定同步速度;建议副本部署在同一机房。
写入一致性默认最终一致;支持 insert_quorum 实现强一致SET insert_quorum = 2; INSERT INTO ...insert_quorum 要求多数副本确认写入成功才返回,增加延迟。
只读副本可配置副本仅用于查询(不参与写入)无特殊语法;通过应用层路由实现适用于读多写少场景,分担主副本压力。
副本状态监控查询 system.replicas关注字段:is_leaderqueue_sizelast_queue_updateabsolute_delayqueue_size > 0 表示同步延迟;absolute_delay > 300 需告警。
ClickHouse KeeperClickHouse 自研 ZooKeeper 兼容实现(v21.8+)启用 <keeper_server> 配置块降低外部依赖;性能与 ZK 相当,推荐新集群使用。

⚠️ ZooKeeper 是副本协调的单点依赖;ZK 不可用时,写入阻塞,但查询仍可读。

5.2 分片(Sharding)策略

策略说明配置方式注意事项
哈希分片按分片键哈希值均匀分布数据Distributed(cluster, db, table, cityHash64(shard_key))推荐使用 cityHash64()jumpConsistentHash();避免 rand()
显式分片应用层指定目标分片INSERT INTO dist_table VALUES (shard_num, ...)需 Distributed 表第一列为 _shard_num UInt32;灵活性高但复杂。
分片键选择应为高基数、均匀分布的业务主键user_iddevice_idorder_id避免低基数字段(如 country),否则导致数据倾斜。
本地表命名各节点创建同名本地表(如 events_localCREATE TABLE events_local ON CLUSTER ...Distributed 表指向该本地表名。
分布式表作用透明代理查询到所有分片CREATE TABLE events_all AS events_local ENGINE = Distributed(...);本身不存储数据;是查询入口。
分片数量规划初始分片数应满足未来 1~2 年容量建议 2~16 个分片起步分片数一旦确定,扩容需迁移数据(见 5.4 节)。

✅ **最佳实践:**分片键 = 高频 GROUP BY / JOIN 键,避免跨分片重分布。

5.3 故障恢复机制

故障类型恢复机制操作步骤注意事项
单节点宕机(有副本)自动切换到健康副本无需人工干预;查询自动路由到存活节点需确保 Distributed 表配置了多个副本地址。
ZooKeeper 宕机写入暂停,查询仍可读1. 恢复 ZK 集群
2. ClickHouse 自动重连
若 ZK 数据丢失,需重建副本元数据(危险!)。
磁盘损坏(无副本)从备份恢复或重建1. 停止服务
2. 从快照/备份恢复 /var/lib/clickhouse/
3. 启动服务
强烈建议开启副本或定期备份。
副本数据不一致手动修复或重建1. SYSTEM RESTORE REPLICA table(若 ZK 完好)
2. 或删除本地数据目录,重启触发全量复制
需确保至少一个副本数据完整。
网络分区(Split-Brain)依赖 ZK 会话超时判定ZK 会选举新 leader;旧 leader 自动降级避免手动强制写入孤立节点。
查询节点(Initiator)故障客户端重试到其他节点应用层实现重试逻辑可搭配负载均衡器(如 HAProxy)自动 failover。

🛡️ 预防措施:

  • 启用副本(≥2)
  • 监控 system.replicas.absolute_delay
  • 定期备份元数据(/var/lib/clickhouse/metadata/)和关键数据

5.4 扩容与数据迁移

操作步骤工具/命令注意事项
增加分片(水平扩容)1. 在 config.xml 添加新 shard
2. 新节点部署 ClickHouse
3. 创建本地表
4. 迁移历史数据
使用 INSERT INTO new_shard SELECT * FROM old_shard WHERE shard_key % new_shard_count = new_index扩容后分片键哈希空间变化,必须重分布旧数据,否则查询结果错误。
迁移单表数据从旧集群导出 → 导入新集群(见代码示例 43)Native 格式最快且保类型;适合 TB 级迁移。
在线重分片(无停机)1. 双写新旧集群
2. 回溯补数据
3. 切读流量
需应用层支持双写;使用 materialized view 或 Kafka 中转复杂但可实现零停机;适合核心业务。
使用 ALTER TABLE ... MOVE PARTITION将分区移动到另一张表(同实例)ALTER TABLE src MOVE PARTITION '202501' TO dest;仅限同一 ClickHouse 实例内;不能跨节点。
删除旧分片确认数据迁移完成后,下线旧节点1. 从 cluster 配置移除
2. 停止服务
3. 清理数据
先保留 7 天再物理销毁,以防回滚。
自动化工具Altinity/clickhouse-backup、ch-parallel-replica开源工具支持备份/恢复/迁移生产环境建议封装为运维脚本。

代码示例 43:迁移单表数据

# 导出
clickhouse-client --host old --query="SELECT * FROM t FORMAT Native" > t.native

# 导入
clickhouse-client --host new --query="INSERT INTO t FORMAT Native" < t.native

⚠️ 关键原则:

  • 分片数变更 = 架构重大变更,应尽量避免
  • 扩容前做好容量规划(CPU/内存/磁盘/网络)
  • 迁移期间密切监控 system.query_logsystem.replicas