主题
第 2 章 客户端与基础 SQL
学习目标:能用
clickhouse-client、HTTPcurl、JDBC 三种姿势连上 ClickHouse;能讲清「库 / 表 / 视图」三级结构和SHOW/DESCRIBE/SYSTEM命令族;能写出最常用的 DDL / DML / DQL;能解释 ClickHouse SQL 方言与标准 SQL 的关键差异(事务、Mutation、LIMIT BY、SAMPLE、WITH FILL、FORMAT 全家桶)。
2.0 写在前面
第 1 章我们讲清楚了「ClickHouse 为什么快」 —— 列存 + 向量化 + 压缩。但是再快的引擎,得先有个方向盘才能开起来。本章就是介绍三个方向盘:
clickhouse-client—— 官方 CLI,所有 DBA / 开发者必备;- HTTP 接口(8123) —— ClickHouse 的「万能入口」,curl / 浏览器 / Python
requests/ 任何语言都能用; - JDBC / ODBC —— 给 Java / BI 工具用,本章只点一下,深度交给第 14 章。
接着我们用统一的 learn_ck 库做演示:建库 → 建表 → 灌数据 → 查询 → 各种 FORMAT 输出 → SYSTEM 命令族。这一章读完,你应该能像用 MySQL 一样自如地用 ClickHouse。
2.1 三种入口:CLI / HTTP / JDBC
2.1.1 入口一:clickhouse-client(官方 CLI)
bash
# 1. 最基础的连接(默认连 9000 TCP)
$ clickhouse-client --host 127.0.0.1 --port 9000 --user default --password ''
# 2. 直接执行一句 SQL(不进交互)
$ clickhouse-client --query "SELECT version()"
24.8.4.13
# 3. 多句 SQL(用 ; 隔开)
$ clickhouse-client --multiquery --query "
CREATE DATABASE IF NOT EXISTS learn_ck;
USE learn_ck;
SHOW TABLES;
"
# 4. 流式灌数据:本地 CSV 文件灌进表里
$ clickhouse-client --query "INSERT INTO learn_ck.events FORMAT CSVWithNames" < events.csv
# 5. 把 SELECT 结果导出到 CSV
$ clickhouse-client --query "SELECT * FROM learn_ck.events FORMAT CSVWithNames" > out.csv
# 6. 进入交互模式
$ clickhouse-client --host 127.0.0.1
ClickHouse client version 24.8.4.13.
Connecting to 127.0.0.1:9000 as user default.
learn_ck :) SELECT 1;
┌─1─┐
1. │ 1 │
└───┘
1 row in set. Elapsed: 0.001 sec.
learn_ck :) :) help -- 元命令前缀是冒号
learn_ck :) :) exit几个高频参数:
| 参数 | 作用 |
|---|---|
--host / --port | 服务端地址 / TCP 端口(默认 9000) |
--user / --password | 用户名 / 密码 |
--database / -d | 默认连接库 |
--query / -q | 只执行 SQL 不进交互 |
--multiquery | 允许一次发多条 SQL |
--format | 默认 FORMAT(PrettyCompact / TSV / JSON 等) |
--time | 显示每条 SQL 的耗时 |
--echo | 把发的 SQL 也打到 stdout(脚本里方便定位) |
--progress | 显示读取行数与吞吐进度条 |
--max_memory_usage | 单查询最大内存 |
--send_logs_level | 把服务端日志(trace/debug/info/warning/error)回传客户端 |
📌 调试小技巧:开
--send_logs_level=trace,你就能在客户端看到查询经过的每一个算子、每一个 Pipeline 阶段,对学习内核非常友好。
2.1.2 入口二:HTTP 接口(8123)
ClickHouse 的 8123 端口本质上是个超简化的 REST API:任何能发 HTTP 请求的东西都能用。
bash
# 最简:用 GET ?query=
$ curl 'http://127.0.0.1:8123/?query=SELECT version()'
24.8.4.13
# POST:把 SQL 放 body(推荐,复杂 SQL 不需要 URL 编码)
$ curl 'http://127.0.0.1:8123/' -d 'SELECT count(*) FROM system.tables'
180
# 带认证(user/password 走 HTTP Basic Auth)
$ curl -u 'default:' 'http://127.0.0.1:8123/?query=SELECT 1'
# 指定数据库 + FORMAT
$ curl 'http://127.0.0.1:8123/?database=learn_ck' \
-d 'SELECT name, engine FROM system.tables LIMIT 3 FORMAT JSONEachRow'
# 灌数据(最常用模式之一)
$ curl 'http://127.0.0.1:8123/?query=INSERT%20INTO%20learn_ck.events%20FORMAT%20CSV' \
--data-binary @events.csv
# 健康检查(CK 标准做法)
$ curl http://127.0.0.1:8123/ping
Ok.
# 详细 metrics(用于 Prometheus)
$ curl http://127.0.0.1:8123/metricsHTTP 接口的常用 query 参数:
| 参数 | 作用 |
|---|---|
query | 要执行的 SQL |
database | 默认连接的库 |
default_format | 默认 FORMAT |
user / password | 认证(也可走 HTTP Basic Auth) |
query_id | 指定 query_id(方便后续在 system.query_log 里追踪) |
max_memory_usage / max_threads | 临时覆盖 settings |
send_progress_in_http_headers | 进度通过 X-ClickHouse-Progress 头返回 |
📌 HTTP 是 ClickHouse 与外部世界的「万能粘合剂」:浏览器、Grafana、Superset、JS 前端、Python
requests、Postman 都能直接用,不需要任何驱动。clickhouse-connectPython 客户端走的也是这个接口。
2.1.3 入口三:JDBC / ODBC(Java / BI 工具)
xml
<!-- Maven 引入官方 JDBC 驱动 -->
<dependency>
<groupId>com.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.6.5</version>
<classifier>http</classifier>
</dependency>java
// JDBC 连接串示例
String url = "jdbc:clickhouse://127.0.0.1:8123/learn_ck";
Connection conn = DriverManager.getConnection(url, "default", "");
ResultSet rs = conn.createStatement().executeQuery("SELECT version()");
while (rs.next()) System.out.println(rs.getString(1));ODBC 主要给 Excel / Tableau / DBeaver 这类 BI 工具用,下载官方 ODBC 驱动后在系统 ODBC 数据源里配就行。本章不展开。
2.1.4 三种入口对比
| 入口 | 端口 | 协议 | 优势 | 劣势 |
|---|---|---|---|---|
clickhouse-client | 9000 | 原生 TCP | 性能最高、功能最全 | 必须装 CLI |
| HTTP | 8123 | HTTP/HTTPS | 万能、无依赖、防火墙友好 | 协议略冗余 |
| JDBC | 8123 (HTTP) / 9000 (TCP) | 二者皆可 | Java 生态 / BI 工具开箱即用 | 仅限 JVM 系语言 |
📌 生产推荐:
- 运维 / DBA →
clickhouse-client- 应用程序(Python / Go / Node) → HTTP(
clickhouse-connect/clickhouse-go等)- Java / BI 工具 → JDBC
2.2 库 / 表 / 视图三级结构
2.2.1 ClickHouse 的对象层级
┌────────────────────────────┐
│ ClickHouse 实例 (cluster) │
└────────────┬───────────────┘
│
┌──────────────────┼─────────────────┐
│ │ │
┌───────────┐ ┌───────────┐ ┌───────────┐
│ database1 │ │ learn_ck │ │ system │ ← 系统库
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
▼ ▼ ▼
Table / View Table / View Table / View
Materialized View Materialized View (只读视图)
Dictionary Dictionary tables / parts /
query_log / ...ClickHouse 是 database → table 两层结构(没有 PostgreSQL 那种 schema 中间层),但跨库可以直接 database.table 写。
2.2.2 三个特殊的内置库
| 库名 | 作用 |
|---|---|
default | 默认库,没指定库时落在这里 |
system | 核心系统库:所有元数据 / 监控视图都在这里(system.tables / parts / query_log / metrics / settings / users ……) |
INFORMATION_SCHEMA / information_schema | SQL 标准定义的元数据库(兼容性提供) |
📌
system库是 ClickHouse 的金矿:所有运维 / 调优都从它开始。例如:
system.tables—— 所有表的元数据system.parts—— MergeTree 表的所有 Part 信息system.query_log—— 查询历史与性能指标system.metrics/system.events—— 实时指标
2.2.3 视图(View)vs 物化视图(MaterializedView)
| 类型 | 是否真实存数据 | 触发时机 | 典型场景 |
|---|---|---|---|
VIEW | ❌ 不存 | 每次查询都重算 | 简化 SQL、行级权限封装 |
MATERIALIZED VIEW | ✅ 存 | 写入源表时自动触发,把变更写入目标表 | 实时聚合、UV、漏斗(第 10 章详讲) |
LIVE VIEW(实验性) | ✅ 缓存 | 数据变化时刷新 | 大屏 / 仪表板 |
sql
-- 普通视图:本质是 SQL 的别名
CREATE VIEW learn_ck.v_top_country AS
SELECT country, count() AS pv
FROM learn_ck.events
GROUP BY country
ORDER BY pv DESC;
-- 物化视图:写源表时自动汇总到目标表
CREATE MATERIALIZED VIEW learn_ck.mv_country_pv
ENGINE = SummingMergeTree
ORDER BY country
AS
SELECT country, count() AS pv
FROM learn_ck.events
GROUP BY country;第 10 章会把物化视图的 POPULATE 坑、链式 MV、AggregatingMergeTree + MV 实时聚合讲到底,本章先有个印象即可。
2.3 SHOW / DESCRIBE / EXISTS:探索元数据
sql
-- 列出所有数据库
SHOW DATABASES;
-- 列出某库下所有表
SHOW TABLES FROM learn_ck;
-- 模糊搜索表名
SHOW TABLES FROM learn_ck LIKE '%order%';
-- 看建表 DDL(最常用!)
SHOW CREATE TABLE learn_ck.events;
-- 看表结构(列名、类型、默认值、CODEC、注释)
DESCRIBE TABLE learn_ck.events;
-- 或简写
DESC learn_ck.events;
-- 判断表是否存在
EXISTS TABLE learn_ck.events; -- → 1
-- 列出系统中所有用户、角色、权限
SHOW USERS;
SHOW ROLES;
SHOW GRANTS;
-- 列出物化视图、字典、函数
SHOW DICTIONARIES;
SHOW FUNCTIONS;SHOW CREATE TABLE 与 MySQL 完全同名同语义,迁移读者能秒上手。
2.4 SYSTEM 命令族
ClickHouse 提供了一套以 SYSTEM 开头的「运维操作 SQL」,对应 MySQL 的 FLUSH TABLES / RESET QUERY CACHE 等。
sql
-- 立刻刷新所有 system.*_log 缓冲(包括 query_log),便于查看最新一条 SQL
SYSTEM FLUSH LOGS;
-- 重新加载 config.xml(无需重启服务)
SYSTEM RELOAD CONFIG;
-- 重新加载用户配置(users.xml / SQL 用户)
SYSTEM RELOAD USERS;
-- 立刻在前台合并某张表的所有 Part(生产慎用!会锁住合并队列)
OPTIMIZE TABLE learn_ck.events FINAL;
-- 临时关停 / 恢复后台合并
SYSTEM STOP MERGES learn_ck.events;
SYSTEM START MERGES learn_ck.events;
-- 临时关停 / 恢复 Mutation(UPDATE/DELETE 异步重写)
SYSTEM STOP MUTATIONS learn_ck.events;
SYSTEM START MUTATIONS learn_ck.events;
-- 临时关停 / 恢复后台 fetch(副本同步,第 13 章)
SYSTEM STOP FETCHES;
SYSTEM START FETCHES;
-- 立刻同步副本(强制等待该副本与 ZK 完全一致)
SYSTEM SYNC REPLICA learn_ck.events_replicated;
-- 重新加载所有字典
SYSTEM RELOAD DICTIONARIES;
-- DROP 缓存(编译过的 SQL 表达式 / mark cache / DNS 等)
SYSTEM DROP MARK CACHE;
SYSTEM DROP UNCOMPRESSED CACHE;
SYSTEM DROP DNS CACHE;📌 生产中最常用的三个:
SYSTEM FLUSH LOGS—— 想立刻在system.query_log看到刚才那条 SQL 的耗时。SYSTEM RELOAD CONFIG—— 改config.xml后无需重启。OPTIMIZE TABLE ... FINAL—— 强制合并;注意这是「重 IO 操作」,生产不要随便跑(第 11 章详讲它的代价)。
2.5 DDL:建库、建表、改结构
2.5.1 库的 DDL
sql
-- 创建库
CREATE DATABASE IF NOT EXISTS learn_ck;
-- 删除库(连同所有表)—— 谨慎!
DROP DATABASE IF EXISTS learn_ck;
-- 重命名(CK 24+ 支持)
RENAME DATABASE learn_ck TO learn_ck_v2;2.5.2 表的 DDL
sql
CREATE TABLE learn_ck.events
(
event_id UInt64 COMMENT '事件 ID',
user_id UInt32,
country LowCardinality(String),
url String CODEC(ZSTD(3)),
bytes UInt32 CODEC(T64, LZ4),
ts DateTime CODEC(DoubleDelta, ZSTD(3))
)
ENGINE = MergeTree -- 必须显式指定引擎
PARTITION BY toYYYYMM(ts) -- 按月分区
ORDER BY (country, ts) -- 排序键 = 主键 = 稀疏索引键
SETTINGS index_granularity = 8192; -- 默认就 8192,可不写
-- 删表
DROP TABLE IF EXISTS learn_ck.events;
-- 截断表(保留结构,清空数据)
TRUNCATE TABLE learn_ck.events;
-- 重命名(同库内)
RENAME TABLE learn_ck.events TO learn_ck.events_v2;
-- 跨库重命名(注意是 RENAME TABLE 而非 ALTER TABLE)
RENAME TABLE learn_ck.events TO other_db.events;2.5.3 ALTER TABLE:改列、加索引、改 TTL
sql
-- 加列(ClickHouse 支持,且对已有数据无锁、即时生效)
ALTER TABLE learn_ck.events ADD COLUMN browser String DEFAULT '';
-- 改列类型(兼容性变更瞬时完成;不兼容会触发 Mutation 重写)
ALTER TABLE learn_ck.events MODIFY COLUMN bytes UInt64;
-- 改列默认值
ALTER TABLE learn_ck.events MODIFY COLUMN bytes UInt64 DEFAULT 0;
-- 删列
ALTER TABLE learn_ck.events DROP COLUMN browser;
-- 加 / 删跳数索引(第 5 章详讲)
ALTER TABLE learn_ck.events
ADD INDEX idx_url url TYPE bloom_filter(0.01) GRANULARITY 4;
ALTER TABLE learn_ck.events DROP INDEX idx_url;📌 与 MySQL 的最大区别:MySQL
ALTER TABLE改列类型可能要锁表数小时;ClickHouse 大多数 ADD/DROP/MODIFY COLUMN 是「只改元数据」秒级完成;只有需要重写每行数据的变更才会变成 Mutation(第 12 章)。
2.6 DML:写入、更新、删除
2.6.1 INSERT VALUES(适合演示,生产慎用单条)
sql
INSERT INTO learn_ck.events (event_id, user_id, country, url, bytes, ts) VALUES
(1, 1001, 'BJ', '/home', 1500, '2026-04-17 09:00:00'),
(2, 1002, 'SH', '/cart', 900, '2026-04-17 09:00:01'),
(3, 1003, 'GZ', '/order', 3200, '2026-04-17 09:00:02');📌 生产警告:单条 INSERT 是反模式,每次都会生成一个 Part;批量写至少 1 万行一批(第 7 章详讲)。
2.6.2 INSERT FORMAT(高性能批量灌数据)
bash
# CSV 灌入
$ clickhouse-client --query "INSERT INTO learn_ck.events FORMAT CSV" < events.csv
# JSONEachRow 灌入(每行一个 JSON 对象)
$ cat events.jsonl | clickhouse-client --query "INSERT INTO learn_ck.events FORMAT JSONEachRow"
# Parquet 灌入(CK 23+ 性能极佳)
$ clickhouse-client --query "INSERT INTO learn_ck.events FORMAT Parquet" < events.parquet
# HTTP 灌入
$ curl -X POST 'http://127.0.0.1:8123/?query=INSERT INTO learn_ck.events FORMAT CSVWithNames' \
--data-binary @events.csv2.6.3 INSERT SELECT(最强的 ETL 写法)
sql
-- 从一张表导到另一张表(同库 / 跨库 / 跨集群都能写)
INSERT INTO learn_ck.events_clean
SELECT * FROM learn_ck.events_raw
WHERE bytes > 0;
-- 从远程库导入(第 14 章会详细讲表函数)
INSERT INTO learn_ck.events
SELECT * FROM mysql('mysql.host:3306', 'shop', 'orders', 'user', 'pass');
-- 从 S3 / HDFS / URL 直接拉
INSERT INTO learn_ck.events
SELECT * FROM s3('https://my-bucket.s3.com/events.parquet', 'Parquet');2.6.4 ALTER TABLE … UPDATE / DELETE(Mutation)
sql
-- 「更新」:实质是后台异步重写整个 Part
ALTER TABLE learn_ck.events
UPDATE bytes = 0
WHERE bytes IS NULL;
-- 「删除」:同上
ALTER TABLE learn_ck.events
DELETE WHERE country = 'XX';
-- 24.x 起的「轻量 DELETE」(lightweight DELETE,逻辑标记,立即生效)
DELETE FROM learn_ck.events WHERE country = 'XX';
-- 看 Mutation 进度
SELECT database, table, mutation_id, command, is_done
FROM system.mutations
WHERE NOT is_done;📌 关键差异:
- MySQL/PG 的
UPDATE/DELETE是事务内即时生效。- ClickHouse 的
ALTER TABLE … UPDATE/DELETE是 Mutation,后台异步重写整个 Part,可能耗时几分钟到几小时。- 轻量
DELETE FROM是 24.x 主推方案:仅打上「已删除」逻辑标记,查询时过滤;但仍是异步彻底回收。- 正确姿势:能用
TTL/REPLACE PARTITION解决的,就别用 Mutation;高频改的字段,根本不该放 ClickHouse。
2.7 DQL:SELECT 与 FORMAT 全家桶
2.7.1 SELECT 的基础结构
sql
SELECT
country,
count() AS pv,
uniq(user_id) AS uv,
avg(bytes) AS avg_bytes
FROM learn_ck.events
WHERE ts >= '2026-04-17 00:00:00'
AND ts < '2026-04-18 00:00:00'
GROUP BY country
HAVING pv > 100
ORDER BY pv DESC
LIMIT 10
SETTINGS max_threads = 8; -- 临时覆盖参数ClickHouse 的 SELECT 大多数语法跟 MySQL/PG 相同(包括窗口函数 / CTE / GROUPING SETS / ROLLUP / CUBE),但有几个ClickHouse 特色子句:
sql
-- LIMIT BY:每个 country 取 PV 前 3
SELECT country, url, count() AS pv
FROM events
GROUP BY country, url
ORDER BY country, pv DESC
LIMIT 3 BY country;
-- SAMPLE:只在表的一个采样子集上跑(建表时配 SAMPLE BY 才能用)
SELECT count() FROM events SAMPLE 0.1;
-- WITH FILL:自动补齐缺失的时间序列
SELECT toDate(ts) AS d, count() AS pv
FROM events
GROUP BY d
ORDER BY d
WITH FILL FROM toDate('2026-04-01') TO toDate('2026-04-30') STEP INTERVAL 1 DAY;
-- ARRAY JOIN:把数组字段「炸开」(第 8 章详讲)
SELECT user_id, tag
FROM users
ARRAY JOIN tags AS tag;
-- WITH ROLLUP / CUBE / TOTALS:汇总行
SELECT country, count() FROM events GROUP BY country WITH TOTALS;2.7.2 FORMAT 全家桶
ClickHouse 一大特色是支持 70+ 种 FORMAT 输出 / 输入。常用的:
| FORMAT | 输出样子 | 典型用途 |
|---|---|---|
PrettyCompact | 默认,CLI 漂亮表格 | 人眼看 |
Pretty | 加边框的表格 | 文档 |
TabSeparated (TSV) | Tab 分隔 | shell 脚本管道 |
TabSeparatedWithNames | 带表头的 TSV | 上同 |
CSV | 逗号分隔 | Excel / 老系统 |
CSVWithNames | 带表头的 CSV | 标准导出 |
JSON | 完整 JSON(含 meta / data / statistics) | 前端 / API |
JSONEachRow | 每行一个 JSON 对象 | 流式处理、Kafka、灌数据 |
Parquet | 列式二进制 | 数据湖、对接 Spark |
Arrow | Apache Arrow IPC | 与 pandas / DataFusion 互通 |
Native | ClickHouse 原生二进制 | 跨集群复制最高性能 |
Vertical | 列纵向显示 | 字段太多时调试用 |
Markdown | Markdown 表格 | 直接贴文档 |
PrettyJSONEachRow | 美化的 JSONL | 调试 |
sql
-- 例 1:导成 CSV
SELECT * FROM events LIMIT 100 FORMAT CSVWithNames;
-- 例 2:导成 JSONL(流式)
SELECT * FROM events FORMAT JSONEachRow;
-- 例 3:纵向显示(调试时超好用)
SELECT * FROM events LIMIT 1 FORMAT Vertical;
-- Row 1:
-- ──────
-- event_id: 1
-- user_id: 1001
-- country: BJ
-- url: /home
-- bytes: 1500
-- ts: 2026-04-17 09:00:00
-- 例 4:Markdown 直接贴博客
SELECT country, count() FROM events GROUP BY country FORMAT Markdown;📌 HTTP 接口配 FORMAT:
/?query=SELECT 1 FORMAT JSON或/?default_format=JSONEachRow,外部系统对接神器。
2.8 ClickHouse SQL 方言 vs 标准 SQL 的差异
| 维度 | 标准 SQL / MySQL / PG | ClickHouse |
|---|---|---|
| 事务 | 完整 ACID | 仅单分区 INSERT 原子 |
BEGIN ... COMMIT | ✅ | 几乎不可用(只对 Atomic 库的部分操作有限支持) |
UPDATE / DELETE | 实时生效 | ALTER TABLE … UPDATE/DELETE 异步 Mutation;轻量 DELETE FROM 立即生效但异步回收 |
JOIN | 全场景,优化器强 | 大表 JOIN 弱,推荐宽表 / 字典 / MV |
| 主键唯一约束 | ✅ | ❌ 主键只是排序键 + 稀疏索引 |
| 外键 | ✅ | ❌ 不支持 |
LIMIT BY | ❌ | ✅ ClickHouse 特有,每组取 N 行 |
SAMPLE | ❌ | ✅ ClickHouse 特有,按采样键采样 |
WITH FILL | ❌ | ✅ 自动补齐缺失时间序列 |
ARRAY JOIN | ❌(PG 有 unnest) | ✅ 数组炸开 |
FORMAT 子句 | ❌ | ✅ 内嵌输出格式 |
SETTINGS 子句 | ❌ | ✅ 单条 SQL 临时覆盖参数 |
| 窗口函数 | ✅ 完整 | ✅ 21.3+ 完整 |
| CTE | ✅ | ✅ |
| 自增 | AUTO_INCREMENT / IDENTITY | ❌ 无(建议业务侧 Snowflake / 雪花算法) |
| 字符集 | 多种 | 只有 UTF-8 |
📌 与 MySQL / PG 的对比小框
| 项 | MySQL / PG | ClickHouse |
|---|---|---|
| 看建表 SQL | SHOW CREATE TABLE t | 完全相同:SHOW CREATE TABLE t |
| 看表结构 | DESC t / \d t | DESC t |
| 看库列表 | SHOW DATABASES / \l | SHOW DATABASES |
| 自增主键 | AUTO_INCREMENT / SERIAL | ❌ 无(业务自管 ID) |
| 字符集 | 逐表设 charset / collation | 全局 UTF-8 |
| 截断表 | TRUNCATE TABLE t | TRUNCATE TABLE t |
| 临时表 | CREATE TEMPORARY TABLE | ✅ CREATE TEMPORARY TABLE(仅当前会话可见) |
| 命令行客户端 | mysql / psql | clickhouse-client |
| HTTP 接口 | ❌(需中间件) | ✅ 原生 8123 |
2.9 实操演练
把以下三段代码跑一遍,你就完整掌握了第 2 章。
2.9.1 SQL 演练(02_client_basic/init.sql)
init.sql 会建 events 表并灌 1 万行示例数据,可在 clickhouse-client 里直接执行:
bash
clickhouse-client --multiquery < 02_client_basic/init.sql2.9.2 Python 演练(02_client_basic/code/basic_query.py)
演示 5 个常用查询模式:
- 简单
count/SELECT - 分组聚合 +
LIMIT BY WITH FILL补齐时间序列- 多种
FORMAT输出(PrettyCompact / JSONEachRow / CSV) - 用
SETTINGS临时覆盖参数
bash
python 02_client_basic/code/basic_query.py2.9.3 浏览器交互演示(02_client_basic/demo.html)
打开后能:
- ① HTTP 接口模拟器:在文本框输 SQL,自动构造对应的
curl命令; - ② FORMAT 全家桶预览:同一份示例数据,一键切换 6 种 FORMAT 看输出长啥样;
- ③ SYSTEM 命令速查卡:常用 SYSTEM 命令分类、用法、生产注意事项卡片化展示。
2.10 本章小结
┌─────────────────────────────────────────────────────────────┐
│ 本章核心要点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ① 三种入口:clickhouse-client (9000) / HTTP (8123) / JDBC │
│ - HTTP 是「万能粘合剂」,浏览器到任何语言都能用 │
│ │
│ ② 库 / 表 / 视图三级结构: │
│ - 两层:database → table(无 schema 中间层) │
│ - system 库是金矿(tables/parts/query_log/metrics …) │
│ │
│ ③ DDL:CREATE/DROP/RENAME/TRUNCATE/ALTER │
│ - ALTER 多数情况只改元数据秒级完成 │
│ - 不兼容变更会触发 Mutation │
│ │
│ ④ DML:INSERT VALUES / INSERT FORMAT / INSERT SELECT │
│ - 单条 INSERT 是反模式 │
│ - UPDATE/DELETE 是异步 Mutation │
│ - 24.x 推荐用轻量 DELETE FROM │
│ │
│ ⑤ DQL:SELECT + LIMIT BY / SAMPLE / WITH FILL / ARRAY JOIN │
│ - 70+ 种 FORMAT 全家桶(CSV/JSON/Parquet/Native) │
│ │
│ ⑥ SYSTEM 命令族:FLUSH LOGS / RELOAD CONFIG / OPTIMIZE … │
│ │
└─────────────────────────────────────────────────────────────┘2.11 面试高频题
Q1:ClickHouse 的 HTTP 接口和 TCP 协议有什么区别?什么场景该用哪个?
考察点:是否真的用过两种入口、是否理解协议差异。
标准答案:
| 维度 | TCP(9000) | HTTP(8123) |
|---|---|---|
| 协议 | ClickHouse 自定义二进制 | 标准 HTTP |
| 性能 | 高(无 HTTP 头开销) | 中(每请求多几十字节头) |
| 典型客户端 | clickhouse-client / clickhouse-driver | 浏览器 / curl / clickhouse-connect / JDBC HTTP / Grafana |
| 防火墙 | 需开 9000 | 80/443 通常已开放 |
| 流式 / 批量灌数据 | 性能最好 | 也支持 --data-binary @file 流式 |
| 连接复用 | 长连接 | HTTP Keep-Alive |
| 协议反推 / Wireshark 抓包 | 难(自定义二进制) | 易(明文 HTTP) |
| 进度信息 | 协议帧内置 | X-ClickHouse-Progress 头 |
场景选择:
- 大数据量批量写 / 同集群复制 / DBA 操作 → TCP(性能最好);
- 应用程序 / Web 前端 / BI 工具 / Serverless / 跨网段 → HTTP(兼容性最好);
- Java 生态 → JDBC,二者都可走。
加分项:能补「clickhouse-connect Python 库底层走 HTTP,但通过 LZ4 / ZSTD 压缩 Native 帧也能接近 TCP 性能」。
易错点:不要说「HTTP 一定比 TCP 慢一倍」 —— 现代版本下 HTTP 接口在压缩 Native 格式下与 TCP 性能差距 < 20%。
与 MySQL/PG 对比:MySQL/PG 都只有自定义二进制协议,HTTP 接口需要靠 PostgREST / ProxySQL HTTP 中间件。ClickHouse 把 HTTP 做成原生入口是它「拥抱云原生」的标志性设计。
Q2:ClickHouse 的 INSERT 为什么不能一条一条写?正确姿势是什么?
考察点:MergeTree 写入路径理解,是日常踩坑高发题。
标准答案:
每次 INSERT 都会生成一个新的 Part 目录(包含所有列文件、主键索引、Mark 索引、checksums.txt 等)。代价:
- 元数据膨胀:1 万次单条 INSERT = 1 万个 Part 目录 = 几十万小文件,文件系统压力巨大;
- 后台 Merge 跟不上:单分区超过 300 个未合并 Part 会拒绝写入(
Too many parts); - ZK / Keeper 压力(ReplicatedMergeTree):每个 Part 都要写 Keeper;
- 稀疏索引浪费:每个 Part 至少 1 个 Mark = 8192 行的索引开销,单条 INSERT 完全浪费索引空间。
正确姿势:
- 客户端批量:一次 INSERT 至少 1 万行,10 万 ~ 100 万更佳;
async_insert服务端缓冲(21.11+):开启后多个小 INSERT 在服务端聚合成大 Block 后落盘;Buffer引擎:在主表前面加一个 Buffer 表代理,由 Buffer 自动攒批;- 业务侧 Kafka + 消费:业务先写 Kafka,下游消费时按时间窗口攒批。
加分项:能补「async_insert + wait_for_async_insert=0」这套 24.x 推荐组合,让客户端瞬时返回,服务端攒够批再落盘,对 OLTP 风格的业务也能撑住。
易错点:不要说「INSERT 慢是因为索引重建」—— 主键索引是稀疏的,并不重,慢的根源是 Part 数膨胀。
与 MySQL/PG 对比:MySQL/PG 单条 INSERT 是日常;ClickHouse 是反模式。这是 OLTP 与 OLAP 引擎设计哲学的分水岭。
Q3:ClickHouse 的 UPDATE / DELETE 与 MySQL 有什么区别?什么时候应该用 / 不该用?
考察点:Mutation 机制的理解。
标准答案:
ClickHouse 有三种修改方式:
ALTER TABLE … UPDATE/DELETE(Mutation):- 后台异步任务;
- 实质是把所有命中条件的 Part 整个重写一遍(即使只改一行 也得重写整个 Part);
- 进度可在
system.mutations看; - 大表 Mutation 可能持续数小时,期间持续吃 IO + CPU。
- 轻量
DELETE FROM(CK 22.8+):- 仅在 Part 上加一个逻辑删除位图,立即生效;
- 真正的物理回收仍在后台异步合并时完成;
- 比 Mutation 快几个数量级,但对频繁删除小批量数据才合适。
ALTER TABLE … REPLACE PARTITION(强烈推荐):- 直接把整个分区原子替换,对历史数据修正最优雅;
- 例:「把 2026-04-17 这一天的数据重新跑一遍 ETL 然后整盘替换」。
该用 / 不该用:
✅ 该用:偶发的「数据修复」「GDPR 删除」「批量回填」 —— 每月几次。 ❌ 不该用:高频字段更新、用户状态变更、订单状态机 —— 这些就该放 MySQL。
加分项:能解释为什么 Mutation 是「异步重写整 Part」 —— 因为列存数据是压缩 + 顺序写的,无法原地修改,只能重写。
易错点:不要说「轻量 DELETE 是真删除」 —— 它只是逻辑标记,物理回收依然异步。
与 MySQL/PG 对比:MySQL/PG 的 UPDATE/DELETE 是事务内即时生效;ClickHouse 的是异步重写。本质差异来自「行存可原地改 vs 列存只能整段重写」。
Q4:什么是 LowCardinality?什么情况下该用?什么情况下不该用?
考察点:数据类型选型,OLAP 性能调优常考点。
标准答案:
LowCardinality(T) 是 ClickHouse 给「取值集合较小(通常 < 10000)的列」准备的「字典编码」类型。它把列拆成两部分:
- 字典(dictionary):去重后的取值列表(如
["BJ", "SH", "GZ", "SZ", ...]); - 位置编码(codes):每行存的不是字符串,而是字典里的下标(UInt8 / UInt16 / UInt32)。
好处:
- 存储省:原本每行一个字符串 → 现在每行一个 1~4 字节整数;
- 查询快:GROUP BY、过滤、JOIN 都基于位置编码(整数比较),SIMD 友好;
- 压缩友好:字典 + 位置都极易压缩,整体压缩比 3~20 倍。
该用:
- 国家、城市、省份、性别、状态、客户端类型、HTTP method、广告位 ID、产品类目 ……
- 重复值多、基数 < 10000 的字符串列。
不该用:
- 唯一值很多的字符串(user_id、URL、订单号)—— 字典反而比原值大;
- 数值列 ——
LowCardinality(Int)收益甚微,不要套; - 极少数情况下查询模式是「按 url 模糊搜索」,bloom_filter 跳数索引比 LowCardinality 更合适。
加分项:能补「SET allow_suspicious_low_cardinality_types = 1 才能套在 Int / Float 上,但官方明确不推荐」。
易错点:不要把 LowCardinality 当成「压缩」—— 它本质是「字典编码」,与 LZ4/ZSTD 是两层正交能力,可叠加。
与 MySQL/PG 对比:MySQL 的 ENUM 类似但功能弱;PG 没有原生等价物,要用 lookup table + JOIN 模拟。ClickHouse 的 LowCardinality 是「免运维 ENUM」,可动态新增字典值。
Q5:ClickHouse 的 SQL 方言和标准 SQL / MySQL 比,有哪些「特色」?
考察点:是否真的用 ClickHouse 写过 SQL,能不能列出几个 MySQL 没有的特色子句。
标准答案(任选 4~5 个能讲清即可):
LIMIT BY:每个分组取 N 行。例:LIMIT 3 BY country—— 每个国家取 PV 前 3。MySQL 要写复杂窗口函数才能实现。SAMPLE:基于建表时指定的SAMPLE BY列做采样查询。例:SELECT count() FROM t SAMPLE 0.1—— 只跑 10% 数据。WITH FILL:自动补齐时间序列里缺失的日期 / 小时。sqlORDER BY d WITH FILL FROM '2026-04-01' TO '2026-04-30' STEP INTERVAL 1 DAYARRAY JOIN:把数组列「炸开」成多行。等价 PG 的unnest,MySQL 没有。SETTINGS:单条 SQL 临时覆盖参数:sqlSELECT ... SETTINGS max_threads = 8, max_memory_usage = 10000000000FORMAT:内嵌输出格式(CSV / JSON / Parquet / Markdown / Vertical / 70+ 种)。PREWHERE:MergeTree 专属,比WHERE更早执行,先过滤再读其它列,进一步省 IO。GROUP BY ... WITH ROLLUP / CUBE / TOTALS:标准 SQL 也有,但 CK 实现更高效。- 大量丰富的聚合函数:
uniq/uniqExact/quantile/topK/argMax/groupArray/groupUniqArray(第 8 章详讲)。
加分项:能补「这些特色子句都是为 OLAP 场景特别加的」—— 例如时序补齐、分组限流、采样估算,都是分析师高频需求。
易错点:不要说「ClickHouse SQL 跟 MySQL 完全兼容」—— 它没有外键、没有完整事务、没有 AUTO_INCREMENT、JOIN 弱、UPDATE 是 Mutation。
与 MySQL/PG 对比:MySQL 没有 LIMIT BY / SAMPLE / WITH FILL / ARRAY JOIN / PREWHERE;PG 有 unnest 替代 ARRAY JOIN、有 FETCH FIRST WITH TIES 替代 LIMIT BY 部分功能。
📌 下一章预告:第 3 章我们深入数据类型 —— 数值 / 字符串 / 时间 / UUID / Enum / Array / Tuple / Map / Nested / Nullable 的代价、
AggregateFunction,以及类型转换族。这是后面 MergeTree 家族进阶的「弹药库」。
🎬 可视化演示
演示加载缓慢或样式异常?点此在新标签页打开 ↗
💻 示例代码
python
"""
basic_query.py —— 第 2 章配套代码:5 种 ClickHouse 常用查询模式
涵盖:
1) 简单 count / SELECT
2) 分组聚合 + LIMIT BY(每组取前 N)
3) WITH FILL 自动补齐时间序列
4) 用 SETTINGS 子句临时覆盖参数(max_threads / max_memory_usage)
5) 多种 FORMAT 输出(PrettyCompact / JSONEachRow / CSVWithNames / Vertical)
运行前提:
- ClickHouse 已在 127.0.0.1:8123 启动
- 已经执行过 02_client_basic/init.sql(建好 learn_ck.events 表并灌好 1 万行)
- pip install -r requirements.txt
运行:
python 02_client_basic/code/basic_query.py
"""
from __future__ import annotations
import sys
try:
import clickhouse_connect
except ImportError:
sys.stderr.write(
"❌ 缺少依赖 clickhouse-connect,请先执行:\n"
" pip install -r requirements.txt\n"
)
sys.exit(1)
CK = dict(host="127.0.0.1", port=8123, username="default", password="", database="learn_ck")
def hr(title: str) -> None:
print("\n" + "=" * 70)
print(f" {title}")
print("=" * 70)
def demo_1_count(client) -> None:
hr("DEMO 1: 最简 count + SELECT,验证表是否就绪")
rows = client.query("SELECT count() FROM events").result_rows
print(f"events 表共 {rows[0][0]:,} 行")
rows = client.query("SELECT * FROM events ORDER BY ts LIMIT 3").result_rows
print("\n前 3 行示例:")
for r in rows:
print(" ", r)
def demo_2_groupby_limit_by(client) -> None:
hr("DEMO 2: GROUP BY + LIMIT BY —— 每个 country 取 PV 前 3 的 URL")
sql = """
SELECT country, url, count() AS pv
FROM events
GROUP BY country, url
ORDER BY country, pv DESC
LIMIT 3 BY country
"""
rows = client.query(sql).result_rows
cur_country = None
for country, url, pv in rows:
if country != cur_country:
print(f"\n[{country}]")
cur_country = country
print(f" {url:<20} pv={pv}")
def demo_3_with_fill(client) -> None:
hr("DEMO 3: WITH FILL —— 自动补齐缺失的小时(即使该小时没有事件)")
sql = """
SELECT toStartOfHour(ts) AS h, count() AS pv
FROM events
WHERE ts BETWEEN toDateTime('2026-04-01 00:00:00')
AND toDateTime('2026-04-01 12:00:00')
GROUP BY h
ORDER BY h
WITH FILL
FROM toDateTime('2026-04-01 00:00:00')
TO toDateTime('2026-04-01 12:00:00')
STEP toIntervalHour(1)
"""
rows = client.query(sql).result_rows
print("小时 | pv")
print("-" * 32)
for h, pv in rows:
bar = "█" * min(int(pv / 5), 30)
print(f" {h} {pv:>4} {bar}")
def demo_4_settings(client) -> None:
hr("DEMO 4: SETTINGS 子句 —— 临时覆盖 max_threads")
sql = """
SELECT browser, count() AS pv, avg(response_ms) AS avg_ms
FROM events
GROUP BY browser
ORDER BY pv DESC
SETTINGS max_threads = 2, max_memory_usage = 1000000000
"""
res = client.query(sql)
print(f"{'browser':<12} {'pv':>8} {'avg_ms':>10}")
print("-" * 32)
for browser, pv, avg_ms in res.result_rows:
print(f"{browser:<12} {pv:>8} {avg_ms:>10.1f}")
print(f"\n服务端汇报:扫描 {res.summary.get('read_rows','?')} 行;"
f"耗时 {int(res.summary.get('elapsed_ns', 0)) / 1e6:.2f} ms")
def demo_5_formats(client) -> None:
hr("DEMO 5: FORMAT 全家桶 —— 同一份数据 4 种格式输出")
base_sql = "SELECT country, count() AS pv FROM events GROUP BY country ORDER BY pv DESC LIMIT 3"
print("\n--- FORMAT PrettyCompact(CLI 默认,人眼好看)---")
print(client.raw_query(f"{base_sql} FORMAT PrettyCompact").decode())
print("--- FORMAT JSONEachRow(流式 JSON Lines)---")
print(client.raw_query(f"{base_sql} FORMAT JSONEachRow").decode())
print("--- FORMAT CSVWithNames(导出 / Excel 友好)---")
print(client.raw_query(f"{base_sql} FORMAT CSVWithNames").decode())
print("--- FORMAT Vertical(字段太多时调试用)---")
print(client.raw_query(f"SELECT * FROM events ORDER BY ts LIMIT 1 FORMAT Vertical").decode())
def main() -> None:
try:
client = clickhouse_connect.get_client(**CK)
except Exception as e:
sys.stderr.write(f"❌ 连接失败:{e}\n")
sys.exit(1)
if not client.query("EXISTS TABLE learn_ck.events").result_rows[0][0]:
sys.stderr.write(
"❌ learn_ck.events 表不存在,请先执行:\n"
" clickhouse-client --multiquery < 02_client_basic/init.sql\n"
)
sys.exit(1)
demo_1_count(client)
demo_2_groupby_limit_by(client)
demo_3_with_fill(client)
demo_4_settings(client)
demo_5_formats(client)
print("\n🎉 第 2 章实操完成。打开 02_client_basic/demo.html 在浏览器里看可视化演示。")
if __name__ == "__main__":
main()