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 数据”时,秒级返回。

小技巧:标签数量建议控制在 10 个以内。太多会影响写入性能。我曾经见过有人设了 30 多个标签,结果写入速度直接腰斩。

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 数据来说,毫秒级就够了。但如果你做高频交易,可能需要微秒级。我建议根据业务场景来定,精度越高,存储开销越大。

避坑指南:我曾经遇到过一个问题——同一张表里插入了两个相同时间戳的数据。TDengine 默认会覆盖旧数据。如果你需要保留所有数据,记得设置时间戳唯一性约束,或者用序列号做辅助。

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 的骨架。

嗯,讲到这里,你应该对 TDengine 的核心设计有了整体认识。下一节咱们会深入聊聊数据写入和查询的最佳实践。到时候我会拿实际项目中的 Tick 数据来演示,保证让你看完就能上手。

TDengine 核心概念体系 超级表 (Super Table) 子表 (Child Table) 标签 (Tags) 时间戳主键 (TS PK) 列式存储 数据分片 数据分区 超级表定义结构,子表承载数据,标签用于过滤,时间戳保证有序 列式存储提升查询效率,分片分区实现水平扩展

专注资料整理