Skip to content

第 2 章 客户端与基础 SQL

学习目标:能用 clickhouse-client、HTTP curl、JDBC 三种姿势连上 ClickHouse;能讲清「库 / 表 / 视图」三级结构和 SHOW / DESCRIBE / SYSTEM 命令族;能写出最常用的 DDL / DML / DQL;能解释 ClickHouse SQL 方言与标准 SQL 的关键差异(事务、Mutation、LIMIT BYSAMPLEWITH FILL、FORMAT 全家桶)。


2.0 写在前面

第 1 章我们讲清楚了「ClickHouse 为什么快」 —— 列存 + 向量化 + 压缩。但是再快的引擎,得先有个方向盘才能开起来。本章就是介绍三个方向盘:

  1. clickhouse-client —— 官方 CLI,所有 DBA / 开发者必备;
  2. HTTP 接口(8123) —— ClickHouse 的「万能入口」,curl / 浏览器 / Python requests / 任何语言都能用;
  3. 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/metrics

HTTP 接口的常用 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-connect Python 客户端走的也是这个接口。

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-client9000原生 TCP性能最高、功能最全必须装 CLI
HTTP8123HTTP/HTTPS万能、无依赖、防火墙友好协议略冗余
JDBC8123 (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_schemaSQL 标准定义的元数据库(兼容性提供)

📌 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;

📌 生产中最常用的三个

  1. SYSTEM FLUSH LOGS —— 想立刻在 system.query_log 看到刚才那条 SQL 的耗时。
  2. SYSTEM RELOAD CONFIG —— 改 config.xml 后无需重启。
  3. 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.csv

2.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/DELETEMutation,后台异步重写整个 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
ArrowApache Arrow IPC与 pandas / DataFusion 互通
NativeClickHouse 原生二进制跨集群复制最高性能
Vertical列纵向显示字段太多时调试用
MarkdownMarkdown 表格直接贴文档
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 / PGClickHouse
事务完整 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 / PGClickHouse
看建表 SQLSHOW CREATE TABLE t完全相同SHOW CREATE TABLE t
看表结构DESC t / \d tDESC t
看库列表SHOW DATABASES / \lSHOW DATABASES
自增主键AUTO_INCREMENT / SERIAL❌ 无(业务自管 ID)
字符集逐表设 charset / collation全局 UTF-8
截断表TRUNCATE TABLE tTRUNCATE TABLE t
临时表CREATE TEMPORARY TABLECREATE TEMPORARY TABLE(仅当前会话可见)
命令行客户端mysql / psqlclickhouse-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.sql

2.9.2 Python 演练(02_client_basic/code/basic_query.py

演示 5 个常用查询模式:

  1. 简单 count / SELECT
  2. 分组聚合 + LIMIT BY
  3. WITH FILL 补齐时间序列
  4. 多种 FORMAT 输出(PrettyCompact / JSONEachRow / CSV)
  5. SETTINGS 临时覆盖参数
bash
python 02_client_basic/code/basic_query.py

2.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
防火墙需开 900080/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. 元数据膨胀:1 万次单条 INSERT = 1 万个 Part 目录 = 几十万小文件,文件系统压力巨大;
  2. 后台 Merge 跟不上:单分区超过 300 个未合并 Part 会拒绝写入(Too many parts);
  3. ZK / Keeper 压力(ReplicatedMergeTree):每个 Part 都要写 Keeper;
  4. 稀疏索引浪费:每个 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 有三种修改方式

  1. ALTER TABLE … UPDATE/DELETE(Mutation)
    • 后台异步任务;
    • 实质是把所有命中条件的 Part 整个重写一遍(即使只改一行 也得重写整个 Part);
    • 进度可在 system.mutations 看;
    • 大表 Mutation 可能持续数小时,期间持续吃 IO + CPU。
  2. 轻量 DELETE FROM(CK 22.8+):
    • 仅在 Part 上加一个逻辑删除位图,立即生效;
    • 真正的物理回收仍在后台异步合并时完成;
    • 比 Mutation 快几个数量级,但对频繁删除小批量数据才合适。
  3. 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. 存储省:原本每行一个字符串 → 现在每行一个 1~4 字节整数;
  2. 查询快:GROUP BY、过滤、JOIN 都基于位置编码(整数比较),SIMD 友好;
  3. 压缩友好:字典 + 位置都极易压缩,整体压缩比 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 个能讲清即可):

  1. LIMIT BY:每个分组取 N 行。例:LIMIT 3 BY country —— 每个国家取 PV 前 3。MySQL 要写复杂窗口函数才能实现。
  2. SAMPLE:基于建表时指定的 SAMPLE BY 列做采样查询。例:SELECT count() FROM t SAMPLE 0.1 —— 只跑 10% 数据。
  3. WITH FILL:自动补齐时间序列里缺失的日期 / 小时。
    sql
    ORDER BY d WITH FILL FROM '2026-04-01' TO '2026-04-30' STEP INTERVAL 1 DAY
  4. ARRAY JOIN:把数组列「炸开」成多行。等价 PG 的 unnest,MySQL 没有。
  5. SETTINGS:单条 SQL 临时覆盖参数:
    sql
    SELECT ... SETTINGS max_threads = 8, max_memory_usage = 10000000000
  6. FORMAT:内嵌输出格式(CSV / JSON / Parquet / Markdown / Vertical / 70+ 种)。
  7. PREWHERE:MergeTree 专属,比 WHERE 更早执行,先过滤再读其它列,进一步省 IO。
  8. GROUP BY ... WITH ROLLUP / CUBE / TOTALS:标准 SQL 也有,但 CK 实现更高效。
  9. 大量丰富的聚合函数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()

basic_query.py ↗