第 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 | 配置文件需包含 user、password、host 等节点。 |
| 安全连接(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 | 返回结构化 JSON | clickhouse-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 TSVWithNames | TSV + 列名头 | 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)。 |
| 修改 TTL | ALTER 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 IGNORE或ON 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 表示完成;失败需手动处理。 |
| 取消 mutation | KILL 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 JOIN | SELECT 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 JOIN | SELECT ... 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 JOIN | SELECT ... 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 CSV | cat file.csv | clickhouse-client --query="INSERT INTO table FORMAT CSV" | 从标准输入导入 CSV | cat 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 等)
| 输出格式 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| JSONEachRow | SELECT ... FORMAT JSONEachRow | 每行一个 JSON 对象,流式友好 | clickhouse-client --query="SELECT * FROM events LIMIT 10 FORMAT JSONEachRow" > out.json | 字段名作为 key,适合日志管道(如 Filebeat)。 |
| CSV | SELECT ... FORMAT CSV | 标准 CSV 输出 | clickhouse-client --query="SELECT user_id, ts FROM events FORMAT CSV" > data.csv | 字符串自动加双引号;可用 --output_format_csv_delimiter 改分隔符。 |
| CSVWithNames | SELECT ... FORMAT CSVWithNames | CSV + 列名头 | clickhouse-client --query="SELECT * FROM t FORMAT CSVWithNames" > data.csv | 便于 pandas.read_csv() 直接读取。 |
| Parquet | SELECT ... FORMAT Parquet | 列式二进制格式,高效存储 | clickhouse-client --query="SELECT * FROM large_table FORMAT Parquet" > data.parquet | 需 v21.1+;支持嵌套类型;可被 Spark/DuckDB 直接读取。 |
| Arrow | SELECT ... FORMAT Arrow | 内存分析格式 | clickhouse-client --query="SELECT * FROM t FORMAT Arrow" > data.arrow | 适用于 Python(pyarrow)、R 等生态。 |
| TSVWithNamesAndTypes | SELECT ... FORMAT TSVWithNamesAndTypes | TSV + 列名 + 类型 | 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-local与clickhouse-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 服务;性能较低。 |
| 导出到 MySQL | INSERT 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 指令并行计算整列;减少函数调用开销 | 所有内置函数(如 toDate、length、sum)均为向量化实现。 |
| Block(数据块) | 查询执行的基本单元,包含多列的数组(ColumnVector) | 每列是连续内存数组(如 UInt32[]),便于缓存友好访问 | Block 大小受 max_block_size 控制(默认 65536 行)。 |
| 延迟物化(Late Materialization) | 仅在必要时才组合多列或生成最终结果行 | 先在单列上过滤/聚合,最后投影所需字段 | 显著减少中间数据量,尤其在宽表(数百列)场景。 |
| 表达式编译优化 | 查询中的表达式(如 WHERE a + b > 10)被编译为高效执行路径 | 避免解释执行;常量折叠、谓词下推自动应用 | 用户无法直接控制,但可通过 EXPLAIN 查看优化效果。 |
| 并行处理 | 单机内多线程并行处理不同 data parts 或 blocks | 由 max_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 聚合函数的中间状态(如 uniqState、quantileState) | 高性能物化视图底层存储 | 查询时必须用 -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_size、last_queue_update) | 网络中断会导致队列堆积;需监控 lag。 |
| mutations(ALTER DELETE/UPDATE) | 用户发起 DDL 变更 | 异步执行行级修改 | SELECT * FROM system.mutations; | 每个 mutation 触发一次全表合并,成本极高。 |
| 复制日志清理 | ZooKeeper 中旧日志超过保留阈值 | 防止 ZK 节点爆炸 | 由 zookeeper_session_expiration 和 cleanup_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_replication | Distributed 表写入时是否只发一个副本 | ENGINE = Distributed(..., internal_replication = true) | 设为 true(默认)可避免重复写入;由 Replicated 表自动同步。 |
| 副本修复 | 手动从健康副本克隆数据 | SYSTEM RESTORE REPLICA table_name; | 适用于 ZK 元数据损坏但本地数据完好场景。 |
| ZK 路径清理 | 删除不再使用的表后,ZK 路径不会自动清除 | 需手动清理或使用 system.drop_replica | 残留路径会占用 ZK 内存;定期审计。 |
| 替代方案:ClickHouse Keeper | ClickHouse 自研 ZK 兼容实现(v21.8+) | 启用 <keeper_server> 配置 | 降低外部依赖;性能与 ZK 相当,推荐新集群使用。 |
🛡️ 高可用建议:
- ZooKeeper 集群与 ClickHouse 节点物理隔离
- 监控 ZK 的
zk_avg_latency和zk_outstanding_requests- 副本数 ≥ 2,跨机架/可用区部署
第 3 章:查询处理流程
3.1 查询解析与优化
| 阶段 | 说明 | 优化行为 | 注意事项 |
|---|---|---|---|
| 词法与语法分析 | 将 SQL 字符串解析为抽象语法树(AST) | 报告语法错误(如缺失分号、非法关键字) | 支持 ClickHouse 扩展语法(如 FINAL、SAMPLE、ARRAY JOIN)。 |
| 语义分析 | 绑定表名、列名、函数,验证类型兼容性 | 检查列是否存在、函数是否支持、权限是否满足 | 若列名歧义(多表同名),需显式指定表前缀。 |
| 常量折叠(Constant Folding) | 在解析阶段计算常量表达式 | SELECT 1 + 2 FROM t → SELECT 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.parts 和 system.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 或 blocks | max_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_timetimeout_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 KEY | ORDER 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_threads | SET 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 或 ext4 | XFS 在大文件和高并发下更稳定 | 避免 btrfs、ZFS(除非明确调优)。 |
| 压缩 codec 优化 | CODEC(LZ4HC(9)) 或 CODEC(ZSTD(3)) | 提高压缩比,减少 I/O | LZ4 速度快;ZSTD 压缩率高;可按列指定。 |
| 调整 index_granularity | 建表时 SETTINGS index_granularity = 4096 | 更细索引(适合点查) | 值越小,索引越大,内存占用越高;默认 8192 适合范围查询。 |
| 禁用不必要的合并 | SYSTEM STOP MERGES table;(临时) | 减少写入高峰期 I/O | 仅用于紧急调试;长期禁用会导致查询变慢。 |
💡 监控指标:
system.metrics: MemoryTracking, Querysystem.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_leader、queue_size、last_queue_update、absolute_delay | queue_size > 0 表示同步延迟;absolute_delay > 300 需告警。 |
| ClickHouse Keeper | ClickHouse 自研 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_id、device_id、order_id | 避免低基数字段(如 country),否则导致数据倾斜。 |
| 本地表命名 | 各节点创建同名本地表(如 events_local) | CREATE 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 添加新 shard2. 新节点部署 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_log和system.replicas