4. 数据导入与导出:打通 ClickHouse 的任督二脉
数据导入导出,说白了就是 ClickHouse 的「进出口」业务。我见过不少新手,建表时信心满满,一到导数据就卡壳。其实这块没那么玄乎,掌握几个核心套路就够了。
今天咱们就把 INSERT INTO、文件导入、客户端导入、SELECT 导入、导出到文件这五板斧,一个一个说清楚。
4.1 INSERT INTO 语法:最基础的写入姿势
INSERT INTO 是 ClickHouse 最直接的写入方式。语法上跟标准 SQL 差不多,但有几个细节你得留神。
-- 标准写法:指定列
INSERT INTO table_name (col1, col2, col3) VALUES (1, 'a', now()), (2, 'b', now());
-- 不指定列:按表结构顺序
INSERT INTO table_name VALUES (1, 'a', now()), (2, 'b', now());
-- 从表达式写入
INSERT INTO table_name SELECT * FROM other_table;
ClickHouse 的 INSERT 是异步批量的。你每发一条 INSERT,它不会立刻落盘,而是攒一批再写。这个「攒批」的行为由 max_insert_block_size 控制,默认 1,048,576 行。
4.2 从文件导入:CSV、JSON、Parquet
实际工作中,数据大多躺在文件里。ClickHouse 原生支持三种主流格式:CSV、JSON、Parquet。我个人最常用 Parquet,压缩率高、读取快。
4.2.1 CSV 导入
-- 从 CSV 文件导入
INSERT INTO my_table FORMAT CSV
-- 然后在 clickhouse-client 中粘贴 CSV 内容,或通过管道传入
-- 或者用文件重定向
clickhouse-client --query "INSERT INTO my_table FORMAT CSV" < data.csv
CSV 要注意几个坑:
- 默认分隔符是逗号,字段内有逗号必须用引号包裹
- 空值用
\N表示,不是空字符串 - 表头行不会自动跳过,需要你手动处理
4.2.2 JSON 导入
-- JSONEachRow:每行一个 JSON 对象
INSERT INTO my_table FORMAT JSONEachRow
{"id":1,"name":"Alice","ts":"2024-01-01"}
{"id":2,"name":"Bob","ts":"2024-01-02"}
-- JSONCompact:紧凑格式
INSERT INTO my_table FORMAT JSONCompact
[1,"Alice","2024-01-01"],[2,"Bob","2024-01-02"]
4.2.3 Parquet 导入
-- 直接导入 Parquet 文件
INSERT INTO my_table SELECT * FROM file('data.parquet', 'Parquet')
-- 或者指定路径
INSERT INTO my_table SELECT * FROM file('/data/export/*.parquet', 'Parquet')
Parquet 是列式存储格式,跟 ClickHouse 天生一对。导入速度比 CSV 快 3-5 倍,而且自带 schema,不容易出错。
| 格式 | 导入速度 | 压缩率 | Schema 校验 | 适用场景 |
|---|---|---|---|---|
| CSV | 慢 | 低 | 无 | 小数据、临时调试 |
| JSON | 中等 | 中等 | 弱(字段名匹配) | 日志、API 数据 |
| Parquet | 快 | 高 | 强(自带类型) | 大数据、生产环境 |
4.3 使用 clickhouse-client 导入
clickhouse-client 是 ClickHouse 自带的命令行工具,导入大文件时特别好用。
# 从文件导入 CSV
clickhouse-client --query "INSERT INTO my_table FORMAT CSV" < data.csv
# 从文件导入 Parquet
clickhouse-client --query "INSERT INTO my_table SELECT * FROM file('data.parquet', 'Parquet')"
# 带压缩的导入
gzip -dc data.csv.gz | clickhouse-client --query "INSERT INTO my_table FORMAT CSV"
# 指定输入格式
clickhouse-client --format_csv_delimiter="|" --query "INSERT INTO my_table FORMAT CSV" < data.csv
4.4 使用 INSERT SELECT:表间数据流转
INSERT SELECT 是 ClickHouse 最强大的数据导入方式之一。它允许你从一个表查询数据,直接写入另一个表。
-- 基本用法:从 A 表导入 B 表
INSERT INTO target_table SELECT * FROM source_table
-- 带过滤条件
INSERT INTO target_table SELECT * FROM source_table WHERE event_date > '2024-01-01'
-- 跨集群导入
INSERT INTO target_table SELECT * FROM remote('other_host:9000', 'db', 'source_table', 'user', 'password')
-- 聚合后导入
INSERT INTO agg_table SELECT
toDate(event_time) as day,
count() as cnt
FROM raw_table
GROUP BY day
INSERT SELECT 是流式的。源表读一批,目标表写一批,不会把全部数据加载到内存。所以即使源表有几十亿行,也能平稳导入。
SELECT count() 确认源表数据量,再执行导入。或者先导入到一个临时表,验证无误后再 rename。
4.5 数据导出到文件
导出跟导入是对称的。ClickHouse 支持把查询结果导出成各种格式。
# 导出为 CSV
clickhouse-client --query "SELECT * FROM my_table" --format CSV > export.csv
# 导出为 JSONEachRow
clickhouse-client --query "SELECT * FROM my_table" --format JSONEachRow > export.json
# 导出为 Parquet
clickhouse-client --query "SELECT * FROM my_table" --format Parquet > export.parquet
# 使用 INTO OUTFILE(在 SQL 中指定)
SELECT * FROM my_table INTO OUTFILE '/tmp/export.csv' FORMAT CSV
# 带压缩导出
clickhouse-client --query "SELECT * FROM my_table" --format CSV | gzip > export.csv.gz
导出时有个小技巧:如果你只需要部分列,别用 SELECT *,显式列出需要的列。这样能减少 IO,导出速度翻倍。
4.6 本章小结
数据导入导出是 ClickHouse 的日常操作,但细节决定成败:
- 小数据用 INSERT INTO VALUES,简单直接
- 文件导入优先选 Parquet,又快又稳
- 大文件用 clickhouse-client 配合管道,记得切分并行
- 表间流转用 INSERT SELECT,流式处理不占内存
- 导出用 INTO OUTFILE 或客户端重定向,格式选 Parquet