数据类型与表引擎:打好地基再盖楼
说实话,ClickHouse 的学习曲线不算陡,但有个坎儿必须过——数据类型和表引擎。我见过不少新手,上来就建表、写 SQL,结果跑起来发现性能不对,或者数据对不上。回头一看,全是类型选错、引擎用错的问题。
这一章,咱们就把地基打牢。你想想看,如果连 Int 和 UInt 都分不清,后面谈什么性能优化?
基础数据类型:选对类型,省一半存储
ClickHouse 的数据类型设计,核心思路就一条:能用小的,别用大的。列式存储下,每个字节都乘以行数,省下来的就是真金白银。
整数类型:Int 与 UInt
我最常用的就是 Int8、Int16、Int32、Int64,以及对应的无符号版本 UInt8、UInt16 等。区别很简单:有符号能表示负数,无符号能多一倍正数范围。
| 类型 | 字节 | 范围 | 我常用的场景 |
|---|---|---|---|
| UInt8 | 1 | 0 ~ 255 | 状态码、性别、小枚举 |
| Int16 | 2 | -32768 ~ 32767 | 端口号、年龄 |
| UInt32 | 4 | 0 ~ 42亿 | 用户ID、订单ID |
| Int64 | 8 | -2^63 ~ 2^63-1 | 时间戳(毫秒级) |
浮点数:Float32 与 Float64
Float32 是单精度,Float64 是双精度。日常统计用 Float32 就够了。但注意——浮点数有精度问题。如果你做金额计算,千万别用 Float,用 Decimal。
字符串:String 与 FixedString
String 是变长,存啥都行。FixedString(N) 是定长,适合长度固定的场景,比如 IP 地址、MD5 值。
我个人建议:除非你确定长度不变,否则优先用 String。FixedString 如果存的数据长度不够,会用 \0 补齐,查询时容易出幺蛾子。
日期与时间:Date、DateTime、DateTime64
这三个类型,我几乎每天都要用:
- Date:精确到天,2 字节,存 '2024-01-01' 这种。
- DateTime:精确到秒,4 字节,存 '2024-01-01 12:00:00'。
- DateTime64:精确到毫秒甚至纳秒,适合高频交易、监控数据。
选型很简单:按需取最小。如果只按天统计,用 Date;需要小时级,用 DateTime;要毫秒精度,上 DateTime64。
Nullable 与默认值:小心 Null 的代价
ClickHouse 的 Nullable 和 MySQL 不一样。它本质上是用一个额外的标记列来存“是否为空”。这意味着:Nullable 列会多占存储,而且查询性能会下降。
核心原则:能用默认值代替 Null,就别用 Nullable。
比如用户年龄,用 0 表示未知,比用 Null 好得多。我在一个用户画像项目里,把 5 个 Nullable 列改成默认值,查询速度提升了 30%。
如果你确实需要 Null,语法很简单:
CREATE TABLE example (
id UInt32,
name String,
age Nullable(UInt8) DEFAULT 0
) ENGINE = MergeTree()
ORDER BY id;
注意:Nullable 不能用在索引列上。这是硬限制,别问为什么,记住就行。
MergeTree 家族:ClickHouse 的灵魂
如果说数据类型是砖,那表引擎就是房子的结构。MergeTree 家族是 ClickHouse 最核心、最强大的引擎,没有之一。
我画了一张图,帮你快速理解它们的关系:
MergeTree:一切的基础
MergeTree 的核心机制就两句话:数据按排序键有序存储,后台定期合并小数据块。写入时只管追加,查询时利用排序键做二分查找,快得离谱。
建表语法:
CREATE TABLE events (
event_date Date,
event_time DateTime,
user_id UInt32,
event_type String,
cost Float32
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, event_time);
这里 ORDER BY 就是排序键,也是索引。查询时如果条件匹配排序键前缀,性能最好。
ReplacingMergeTree:去重利器
这个引擎在 MergeTree 基础上加了去重逻辑。合并时,如果排序键相同,只保留最后一条(按版本或时间戳)。
注意:去重只在合并时发生,不是实时的。你刚写入的数据,可能还在小分区里,查出来会有重复。
CREATE TABLE user_status (
user_id UInt32,
status String,
update_time DateTime
) ENGINE = ReplacingMergeTree(update_time)
ORDER BY user_id;
括号里的 update_time 是版本列,合并时保留最大的那条。
FINAL 关键字才解决——但 FINAL 查询很慢,慎用。更好的做法是:在查询时用 argMax 自己取最后一条。
SummingMergeTree:自动求和
这个引擎更狠——合并时,把排序键相同的行,数值列自动求和。适合做 PV、UV、流量统计这类场景。
CREATE TABLE page_views (
page String,
date Date,
views UInt64,
duration Float32
) ENGINE = SummingMergeTree()
ORDER BY (date, page);
写入多条 ('home', '2024-01-01', 1, 2.5),合并后自动变成 ('home', '2024-01-01', N, sum)。
但注意:不是实时的。如果你要精确的实时数据,还是得用普通 MergeTree 自己算。
AggregatingMergeTree:高级聚合
这个引擎不直接存数值,而是存聚合函数的中间状态。配合物化视图使用,可以实现秒级聚合查询。
CREATE TABLE agg_metrics (
date Date,
metric String,
value AggregateFunction(uniq, UInt32)
) ENGINE = AggregatingMergeTree()
ORDER BY (date, metric);
写入时要用 uniqState 函数,查询时用 uniqMerge。说实话,这个引擎有点复杂,我一般只在物化视图里用。
一句话总结四种引擎:
- MergeTree:基础款,啥都能干
- ReplacingMergeTree:去重,适合存最新状态
- SummingMergeTree:自动求和,适合统计
- AggregatingMergeTree:存中间状态,适合高级聚合
嗯,这一章内容不少。数据类型选对了,表引擎用对了,你的 ClickHouse 之路就成功了一半。剩下的,就是多写多练,踩坑多了自然就熟了。