数据类型与表引擎:基础数据类型、MergeTree家族引擎、ReplacingMergeTree与SummingMergeTree实战
聊到ClickHouse,绕不开的就是数据类型和表引擎。这两样东西,说白了就是数据库的「地基」和「骨架」。地基没打好,后面盖多高的楼都悬;骨架选错了,跑起来就费劲。今天咱们就掰开揉碎了聊聊这块内容。
一、基础数据类型:别小看这些「砖块」
ClickHouse的数据类型,乍一看跟别的数据库差不多,但用起来细节挺多。我个人习惯先把常用的几类理清楚:
1. 数值类型
- 整数:UInt8、UInt16、UInt32、UInt64,还有带符号的Int8到Int64。注意,ClickHouse默认用
UInt64做自增ID,但如果你数据量没那么大,用UInt32就够了,能省不少空间。 - 浮点数:Float32、Float64。做量化分析时,价格数据我建议用
Decimal而不是Float,避免精度丢失。你想想看,股票价格精确到小数点后两位,用Float32存久了误差会累积。 - Decimal:
Decimal(P, S),P是总位数,S是小数位数。比如Decimal(18, 4),存价格、成交量这些精确数据很稳。
DateTime64(3)存毫秒级精度,别用Int64自己算,查询时直接按时间过滤,效率高很多。
2. 字符串类型
- String:变长字符串,存股票代码、交易对名称这些。
- FixedString(N):定长字符串。比如股票代码固定6位,用
FixedString(6)比String快,因为不需要记录长度信息。
3. 日期时间类型
- Date:只存日期,占2字节。
- DateTime:存日期+时间,精度到秒。
- DateTime64:支持毫秒、微秒。量化分析中,高频数据必须用这个。
4. 特殊类型
- Array(T):数组类型。比如存一条K线里的多个价格点,用
Array(Float64)很方便。 - Tuple:元组,可以存不同类型的数据组合。
- Nullable:允许空值。但注意,Nullable会额外占用空间,能不用尽量不用。
二、MergeTree家族引擎:核心中的核心
MergeTree是ClickHouse的「王牌引擎」。其他所有引擎,说白了都是它的变种。我刚开始接触时,觉得MergeTree这个名字挺抽象,后来理解了它的工作原理,才发现设计得真巧妙。
它的核心思想是:数据先按分区存储,后台异步合并。写入时很快,查询时按分区裁剪数据,效率极高。
创建MergeTree表的语法很简单:
CREATE TABLE trades (
symbol String,
trade_time DateTime64(3),
price Decimal(18, 4),
volume UInt64
) ENGINE = MergeTree()
ORDER BY (symbol, trade_time)
PARTITION BY toYYYYMM(trade_time);
这里的关键是ORDER BY和PARTITION BY。排序键决定了数据在磁盘上的物理顺序,查询时按这个顺序扫描效率最高。分区键则决定了数据按什么粒度切分。
三、ReplacingMergeTree:去重利器
做量化分析时,经常遇到重复数据。比如从交易所拉数据,偶尔会收到重复的成交记录。这时候ReplacingMergeTree就派上用场了。
它的原理很简单:在后台合并时,如果发现排序键相同的数据,只保留最后一条(按版本号或时间戳判断)。
CREATE TABLE trades_dedup (
symbol String,
trade_time DateTime64(3),
price Decimal(18, 4),
volume UInt64,
version UInt64 -- 版本号,越大越新
) ENGINE = ReplacingMergeTree(version)
ORDER BY (symbol, trade_time)
PARTITION BY toYYYYMM(trade_time);
now64(3)作为版本号。这样合并时,最新的数据会覆盖旧的。但注意,去重不是实时的,是在后台合并时发生的。如果你需要立即去重,可以手动执行OPTIMIZE TABLE,但生产环境不建议频繁操作。
嗯,这里要注意:ReplacingMergeTree的去重是「最终一致性」的。也就是说,刚写入的数据可能还没合并,查询时仍然会看到重复。我一般在查询时加个FINAL修饰符:
SELECT * FROM trades_dedup FINAL WHERE symbol = 'BTCUSDT';
这样查询时会在内存中做一次去重,但性能会受影响。数据量大的时候,我建议还是等后台合并完成,或者自己写逻辑处理。
四、SummingMergeTree:聚合加速器
这个引擎是我在量化分析中最常用的。它的作用是在后台合并时,自动对数值列进行求和。说白了,就是预聚合。
比如你存的是每秒的成交数据,但查询时经常按分钟、小时聚合。用SummingMergeTree,合并时就把相同排序键的数据求和了,查询时直接读聚合后的结果,快得飞起。
CREATE TABLE trades_agg (
symbol String,
bucket_time DateTime, -- 时间桶,比如每分钟
total_volume UInt64,
total_amount Decimal(18, 4),
trade_count UInt64
) ENGINE = SummingMergeTree()
ORDER BY (symbol, bucket_time)
PARTITION BY toYYYYMM(bucket_time);
写入时,你只需要按时间桶写入原始数据:
INSERT INTO trades_agg VALUES
('BTCUSDT', '2024-01-01 10:00:00', 100, 5000.00, 10),
('BTCUSDT', '2024-01-01 10:00:00', 200, 10000.00, 15);
合并后,这两条数据会变成一条:
('BTCUSDT', '2024-01-01 10:00:00', 300, 15000.00, 25)
ReplacingMergeTree或者自己维护。
五、实战对比:什么时候用哪个?
| 场景 | 推荐引擎 | 原因 |
|---|---|---|
| 原始成交数据,需要精确查询 | MergeTree | 保留所有原始记录,不做任何变换 |
| 有重复数据,需要去重 | ReplacingMergeTree | 自动去重,节省存储和查询时间 |
| 按时间桶聚合,如分钟K线 | SummingMergeTree | 预聚合,查询速度提升10倍以上 |
| 既要去重又要聚合 | AggregatingMergeTree | 高级用法,支持自定义聚合函数 |
我个人建议:在量化分析项目中,可以同时建三张表——原始数据用MergeTree,去重后的用ReplacingMergeTree,聚合后的用SummingMergeTree。数据流就是:原始表 → 去重表 → 聚合表。这样各司其职,查询时按需选择。