4. TDengine核心概念:超级表、子表、标签、时间戳主键、列式存储、数据分片与分区
好,咱们今天聊点硬核的。TDengine 之所以能处理海量 Tick 数据,靠的就是它那套独特的数据模型。说白了,它把关系型数据库的表、时序数据的标签、还有分布式系统的分片策略,全揉在了一起。
我第一次接触 TDengine 时,说实话有点懵。超级表?子表?这不就是面向对象里的继承吗?后来用顺手了才发现,这套设计是真的为物联网和金融 Tick 数据量身定做的。咱们一个一个拆开讲。
4.1 超级表(Super Table)—— 数据的“模板”
超级表,你可以把它理解成一个“表模板”。它定义了数据的结构,但自己不存数据。
举个例子。我在做期货 Tick 数据存储时,所有合约都有相同的字段:时间、最新价、成交量、持仓量。但每个合约(比如 rb2401、rb2405)的数据是独立的。这时候,超级表就派上用场了。
-- 创建一个超级表:期货Tick数据模板
CREATE STABLE ticks (
ts TIMESTAMP, -- 时间戳主键
last_price DOUBLE, -- 最新价
volume BIGINT, -- 成交量
open_interest BIGINT -- 持仓量
) TAGS (
symbol VARCHAR(20), -- 合约代码
exchange VARCHAR(10) -- 交易所
);
看到没?CREATE STABLE 就是创建超级表。它把“数据列”和“标签列”分开了。数据列是随时间变化的数值,标签列是固定不变的属性。
4.2 子表(Child Table)—— 真正的数据载体
子表才是真正存数据的地方。每个子表都继承自超级表的结构,但拥有自己独立的标签值。
-- 创建两个子表,分别对应不同合约
CREATE TABLE rb2401 USING ticks TAGS ('rb2401', 'SHFE');
CREATE TABLE rb2405 USING ticks TAGS ('rb2405', 'SHFE');
嗯,这里要注意:子表创建时必须指定标签值。一旦创建,标签值就不能改了(除非你删表重建)。我刚开始做项目时,就踩过这个坑——想着动态修改标签,结果发现不行。
为什么这么设计?你想想看,标签是用来做“静态过滤”的。比如查询某个合约的所有 Tick 数据,TDengine 直接定位到对应的子表,速度极快。如果标签能随便改,那索引就乱套了。
4.3 标签(Tags)—— 数据的“身份证”
标签是 TDengine 最巧妙的设计之一。它把“元数据”和“时序数据”彻底分离了。
- 标签是静态的:一个合约的代码、交易所,不会随时间变化。
- 标签是索引的:查询时按标签过滤,效率极高。
- 标签可以多级:你可以给设备打上“区域”、“类型”、“厂商”等标签。
我个人习惯把标签设计成“查询维度”。比如在股票 Tick 场景中,我会把股票代码、行业板块、市场类型都设为标签。这样查询“所有银行股在 10:30 的 Tick 数据”时,秒级返回。
4.4 时间戳主键(Timestamp Primary Key)
TDengine 里,每张表都必须有一个时间戳列作为主键。这是硬性要求。
为什么?因为时序数据的核心就是“按时间排序”。有了时间戳主键,TDengine 才能做数据压缩、时间范围查询、降采样等操作。
-- 插入数据时,时间戳是必填项
INSERT INTO rb2401 VALUES ('2024-01-15 09:30:00.123', 3985.0, 1200, 50000);
INSERT INTO rb2401 VALUES ('2024-01-15 09:30:00.456', 3986.0, 800, 50000);
注意,时间戳精度可以到毫秒甚至微秒。对于 Tick 数据来说,毫秒级就够了。但如果你做高频交易,可能需要微秒级。我建议根据业务场景来定,精度越高,存储开销越大。
4.5 列式存储(Columnar Storage)
TDengine 用的是列式存储,而不是传统数据库的行式存储。这有什么区别?
| 特性 | 行式存储 | 列式存储 |
|---|---|---|
| 读取方式 | 一次读一整行 | 只读需要的列 |
| 压缩率 | 低(不同类型混在一起) | 高(同类型数据连续存储) |
| 典型场景 | OLTP(事务处理) | OLAP(分析查询) |
| Tick 数据适用性 | 差 | 极好 |
说白了,Tick 数据查询时,你通常只关心“某几个字段”。比如查最新价和成交量,用列式存储就只读这两列,速度飞快。而行式存储会把整行数据都读出来,浪费 IO。
我记得有一次优化查询,把一张 10 亿条记录的 Tick 表从行式迁移到列式,查询时间从 30 秒降到了 0.5 秒。效果立竿见影。
4.6 数据分片与分区(Sharding & Partitioning)
这是 TDengine 分布式能力的核心。单机版用不上,但一旦数据量上来了,你就得懂它。
- 分片(Sharding):把数据水平切分到多个节点上。每个节点负责一部分数据。
- 分区(Partitioning):在单个节点内,按时间范围把数据分成多个文件。
TDengine 默认按时间分区,比如一天一个分区。你还可以按标签做分片,比如把不同合约的数据分到不同节点上。
-- 创建数据库时指定分片和分区策略
CREATE DATABASE tick_db
VGROUPS 4 -- 4个虚拟节点(分片数)
DURATION 1d -- 每个分区1天
KEEP 365d; -- 保留1年数据
这里 VGROUPS 就是分片数。我建议分片数不要超过节点数的 2 倍。比如你有 3 个节点,设 6 个 VGROUPS 就差不多了。太多反而会增加网络开销。
嗯,讲到这里,你应该对 TDengine 的核心设计有了整体认识。下一节咱们会深入聊聊数据写入和查询的最佳实践。到时候我会拿实际项目中的 Tick 数据来演示,保证让你看完就能上手。