文章目录
-
- [1. 为什么需要时序数据库?(与传统数据库对比)](#1. 为什么需要时序数据库?(与传统数据库对比))
- [2. 典型应用场景](#2. 典型应用场景)
- [3. 为什么选 TDengine?](#3. 为什么选 TDengine?)
- [4. 核心概念速览](#4. 核心概念速览)
- [5. 逻辑表与物理表:MySQL vs TDengine](#5. 逻辑表与物理表:MySQL vs TDengine)
-
- [5.1 MySQL:逻辑表与物理表](#5.1 MySQL:逻辑表与物理表)
- [5.2 TDengine:逻辑表与物理表(含时间分区与 vnode 分片)](#5.2 TDengine:逻辑表与物理表(含时间分区与 vnode 分片))
- [6. 核心操作速查](#6. 核心操作速查)
-
- [6.1 建库(分区 + 保留 + 冷热分层)](#6.1 建库(分区 + 保留 + 冷热分层))
- [6.2 建超级表(带缺省值、分片)](#6.2 建超级表(带缺省值、分片))
- [6.3 动态写入(伪代码)](#6.3 动态写入(伪代码))
- [6.4 创建预聚合(TSMA)](#6.4 创建预聚合(TSMA))
- [7. 性能基线](#7. 性能基线)
-
- [时间裁剪示例(假设数据库 `duration 10d`)](#时间裁剪示例(假设数据库
duration 10d))
- [时间裁剪示例(假设数据库 `duration 10d`)](#时间裁剪示例(假设数据库
- [8. 生产部署架构](#8. 生产部署架构)
- [9. 版本与资源](#9. 版本与资源)
1. 为什么需要时序数据库?(与传统数据库对比)
| 对比维度 | 传统关系型数据库 (如 MySQL) | 时序数据库 (以 TDengine 为例) |
|---|---|---|
| 数据本质 | 离散事务,增删改查均衡 | 按时间顺序产生的结构化数据流,写多读少 |
| 时间处理 | 时间只是一个普通字段 | 时间是"第一公民",自动分区、索引、裁剪 |
| 写入能力 | 单机每秒几千~几万行 | 单机每秒可达数百万行 |
| 查询模式 | 多表关联、单条查询 | 按时间范围聚合、降采样、趋势分析 |
| 存储压缩 | 通用压缩,效率一般 | 时序专用压缩,压缩比可达 10~30 倍 |
| 数据保留 | 需手动清理历史数据 | 内置自动过期 (KEEP),按分区直接删除文件 |
核心判断标准 :如果业务是海量设备持续产生带时间戳的数据,且主要查询是看趋势或做统计,就应该选时序数据库。
2. 典型应用场景
- 车联网:每辆车每秒上报 GPS、车速、电池状态等。
- 工业物联网 (IIoT):工厂 PLC、传感器高频采集温度、压力、振动。
- IT 监控:服务器 CPU、内存、网络流量等指标。
- 金融科技:交易行情、风险指标计算。
3. 为什么选 TDengine?
- 独特的"一设备一表"模型:单设备数据物理连续存储,顺序追加(无锁),查询极快。
- 极简运维:内置时间分区、自动分片、冷热分层、预聚合,无需手动分库分表。
- SQL 兼容:学习成本低,团队上手快。
- 单机高性能:合理配置分片,4 核服务器也能支撑海量设备。
- 开源版功能完整:分片、TSMA 预聚合、冷热分层、MQTT 订阅等全部免费。
4. 核心概念速览
| 概念 | 说明 |
|---|---|
| 超级表 (STable) | 定义数据列结构、标签(TAGS)的模板,不存实际数据 |
| 子表 (Subtable) | 每个设备对应一张物理子表,用于存放该设备的所有数据 |
| 标签 (TAGS) | 设备静态属性(如位置、型号),自动建索引,用于分组过滤 |
时间分区 (duration) |
数据库级设置,按天/周/月自动切分物理数据文件 |
分片 (vgroups) |
超级表级设置,将子表打散到多个 vnode 并行处理,单机建议设为 CPU 核数 |
冷热分层 (tiered_storage) |
老数据自动从 SSD 迁移到 S3 对象存储 |
| 预聚合 (TSMA) | 提前计算每小时/每天的统计值,查询聚合结果时毫秒级返回 |
| MQTT 订阅 | TDengine 可作为 MQTT Broker,反向推送数据给外部系统 |
5. 逻辑表与物理表:MySQL vs TDengine
5.1 MySQL:逻辑表与物理表
#mermaid-svg-FAyRN4OGJt3X0hiB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FAyRN4OGJt3X0hiB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FAyRN4OGJt3X0hiB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FAyRN4OGJt3X0hiB .error-icon{fill:#552222;}#mermaid-svg-FAyRN4OGJt3X0hiB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FAyRN4OGJt3X0hiB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FAyRN4OGJt3X0hiB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FAyRN4OGJt3X0hiB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FAyRN4OGJt3X0hiB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FAyRN4OGJt3X0hiB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FAyRN4OGJt3X0hiB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FAyRN4OGJt3X0hiB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FAyRN4OGJt3X0hiB .marker.cross{stroke:#333333;}#mermaid-svg-FAyRN4OGJt3X0hiB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FAyRN4OGJt3X0hiB p{margin:0;}#mermaid-svg-FAyRN4OGJt3X0hiB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FAyRN4OGJt3X0hiB .cluster-label text{fill:#333;}#mermaid-svg-FAyRN4OGJt3X0hiB .cluster-label span{color:#333;}#mermaid-svg-FAyRN4OGJt3X0hiB .cluster-label span p{background-color:transparent;}#mermaid-svg-FAyRN4OGJt3X0hiB .label text,#mermaid-svg-FAyRN4OGJt3X0hiB span{fill:#333;color:#333;}#mermaid-svg-FAyRN4OGJt3X0hiB .node rect,#mermaid-svg-FAyRN4OGJt3X0hiB .node circle,#mermaid-svg-FAyRN4OGJt3X0hiB .node ellipse,#mermaid-svg-FAyRN4OGJt3X0hiB .node polygon,#mermaid-svg-FAyRN4OGJt3X0hiB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FAyRN4OGJt3X0hiB .rough-node .label text,#mermaid-svg-FAyRN4OGJt3X0hiB .node .label text,#mermaid-svg-FAyRN4OGJt3X0hiB .image-shape .label,#mermaid-svg-FAyRN4OGJt3X0hiB .icon-shape .label{text-anchor:middle;}#mermaid-svg-FAyRN4OGJt3X0hiB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FAyRN4OGJt3X0hiB .rough-node .label,#mermaid-svg-FAyRN4OGJt3X0hiB .node .label,#mermaid-svg-FAyRN4OGJt3X0hiB .image-shape .label,#mermaid-svg-FAyRN4OGJt3X0hiB .icon-shape .label{text-align:center;}#mermaid-svg-FAyRN4OGJt3X0hiB .node.clickable{cursor:pointer;}#mermaid-svg-FAyRN4OGJt3X0hiB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FAyRN4OGJt3X0hiB .arrowheadPath{fill:#333333;}#mermaid-svg-FAyRN4OGJt3X0hiB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FAyRN4OGJt3X0hiB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FAyRN4OGJt3X0hiB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FAyRN4OGJt3X0hiB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FAyRN4OGJt3X0hiB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FAyRN4OGJt3X0hiB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FAyRN4OGJt3X0hiB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FAyRN4OGJt3X0hiB .cluster text{fill:#333;}#mermaid-svg-FAyRN4OGJt3X0hiB .cluster span{color:#333;}#mermaid-svg-FAyRN4OGJt3X0hiB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FAyRN4OGJt3X0hiB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FAyRN4OGJt3X0hiB rect.text{fill:none;stroke-width:0;}#mermaid-svg-FAyRN4OGJt3X0hiB .icon-shape,#mermaid-svg-FAyRN4OGJt3X0hiB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FAyRN4OGJt3X0hiB .icon-shape p,#mermaid-svg-FAyRN4OGJt3X0hiB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FAyRN4OGJt3X0hiB .icon-shape .label rect,#mermaid-svg-FAyRN4OGJt3X0hiB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FAyRN4OGJt3X0hiB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FAyRN4OGJt3X0hiB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FAyRN4OGJt3X0hiB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MySQL
1:1
逻辑表:sensor_data
(一张大表,包含所有设备)
物理文件:sensor_data.ibd
(所有设备数据混合存储)
实际例子
sql
-- 创建一张表,所有设备共用
CREATE TABLE sensor_data (
id INT AUTO_INCREMENT PRIMARY KEY,
device_id VARCHAR(20),
ts TIMESTAMP,
current FLOAT
);
-- 设备A、B、C的数据全部混插在一起
INSERT INTO sensor_data (device_id, ts, current) VALUES
('A', NOW(), 10.5),
('B', NOW(), 12.1),
('A', NOW(), 10.7),
('C', NOW(), 9.8);
-- 查设备A的数据,必须全表过滤 device_id
SELECT * FROM sensor_data WHERE device_id = 'A' AND ts > NOW() - INTERVAL 7 DAY;
5.2 TDengine:逻辑表与物理表(含时间分区与 vnode 分片)
#mermaid-svg-SuzLSaLDzVQAZXCG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SuzLSaLDzVQAZXCG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SuzLSaLDzVQAZXCG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SuzLSaLDzVQAZXCG .error-icon{fill:#552222;}#mermaid-svg-SuzLSaLDzVQAZXCG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SuzLSaLDzVQAZXCG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SuzLSaLDzVQAZXCG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SuzLSaLDzVQAZXCG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SuzLSaLDzVQAZXCG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SuzLSaLDzVQAZXCG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SuzLSaLDzVQAZXCG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SuzLSaLDzVQAZXCG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SuzLSaLDzVQAZXCG .marker.cross{stroke:#333333;}#mermaid-svg-SuzLSaLDzVQAZXCG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SuzLSaLDzVQAZXCG p{margin:0;}#mermaid-svg-SuzLSaLDzVQAZXCG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SuzLSaLDzVQAZXCG .cluster-label text{fill:#333;}#mermaid-svg-SuzLSaLDzVQAZXCG .cluster-label span{color:#333;}#mermaid-svg-SuzLSaLDzVQAZXCG .cluster-label span p{background-color:transparent;}#mermaid-svg-SuzLSaLDzVQAZXCG .label text,#mermaid-svg-SuzLSaLDzVQAZXCG span{fill:#333;color:#333;}#mermaid-svg-SuzLSaLDzVQAZXCG .node rect,#mermaid-svg-SuzLSaLDzVQAZXCG .node circle,#mermaid-svg-SuzLSaLDzVQAZXCG .node ellipse,#mermaid-svg-SuzLSaLDzVQAZXCG .node polygon,#mermaid-svg-SuzLSaLDzVQAZXCG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SuzLSaLDzVQAZXCG .rough-node .label text,#mermaid-svg-SuzLSaLDzVQAZXCG .node .label text,#mermaid-svg-SuzLSaLDzVQAZXCG .image-shape .label,#mermaid-svg-SuzLSaLDzVQAZXCG .icon-shape .label{text-anchor:middle;}#mermaid-svg-SuzLSaLDzVQAZXCG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SuzLSaLDzVQAZXCG .rough-node .label,#mermaid-svg-SuzLSaLDzVQAZXCG .node .label,#mermaid-svg-SuzLSaLDzVQAZXCG .image-shape .label,#mermaid-svg-SuzLSaLDzVQAZXCG .icon-shape .label{text-align:center;}#mermaid-svg-SuzLSaLDzVQAZXCG .node.clickable{cursor:pointer;}#mermaid-svg-SuzLSaLDzVQAZXCG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SuzLSaLDzVQAZXCG .arrowheadPath{fill:#333333;}#mermaid-svg-SuzLSaLDzVQAZXCG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SuzLSaLDzVQAZXCG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SuzLSaLDzVQAZXCG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SuzLSaLDzVQAZXCG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SuzLSaLDzVQAZXCG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SuzLSaLDzVQAZXCG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SuzLSaLDzVQAZXCG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SuzLSaLDzVQAZXCG .cluster text{fill:#333;}#mermaid-svg-SuzLSaLDzVQAZXCG .cluster span{color:#333;}#mermaid-svg-SuzLSaLDzVQAZXCG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SuzLSaLDzVQAZXCG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SuzLSaLDzVQAZXCG rect.text{fill:none;stroke-width:0;}#mermaid-svg-SuzLSaLDzVQAZXCG .icon-shape,#mermaid-svg-SuzLSaLDzVQAZXCG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SuzLSaLDzVQAZXCG .icon-shape p,#mermaid-svg-SuzLSaLDzVQAZXCG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SuzLSaLDzVQAZXCG .icon-shape .label rect,#mermaid-svg-SuzLSaLDzVQAZXCG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SuzLSaLDzVQAZXCG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SuzLSaLDzVQAZXCG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SuzLSaLDzVQAZXCG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 物理层自动管理
逻辑层
VNode_2
VNode_1
USING 动态生成
USING 动态生成
数据按时间落入
落入
散列到不同 VNode
落入
超级表 meters
模板,定义列与标签
子表 d_A
子表 d_B
分区: 6.1-6.10
分区: 6.11-6.20
分区: 6.1-6.10
分区: 6.11-6.20
实际例子
sql
-- 1. 建库:每10天一个分区
CREATE DATABASE iot_db duration 10d;
-- 2. 建超级表:分4个vnode(单机4核)
CREATE STABLE meters (
ts TIMESTAMP,
current FLOAT
) TAGS (location INT) vgroups 4;
-- 3. 写入时自动生成子表
INSERT INTO d_A USING meters TAGS (1) VALUES ('2026-06-01 08:00:00', 10.5); -- 落入某vnode的6.1-6.10分区
INSERT INTO d_A USING meters TAGS (1) VALUES ('2026-06-12 08:00:00', 11.2); -- 落入6.11-6.20分区
INSERT INTO d_B USING meters TAGS (2) VALUES ('2026-06-01 08:00:00', 12.1); -- 散列到另一vnode
-- 4. 查询设备A最近7天数据:只扫描d_A相关分区,无关数据自动跳过
SELECT * FROM d_A WHERE ts >= NOW - 7d;
核心区别:
- MySQL 逻辑与物理 1:1 映射,所有设备数据混杂。
- TDengine 逻辑上一设备一子表,物理上数据被时间分区 切分文件、vnode 分片并行处理,单设备查询无需扫描其他设备数据,性能极高。
6. 核心操作速查
6.1 建库(分区 + 保留 + 冷热分层)
sql
-- 定义 S3 磁盘(可选)
CREATE DISK s3_disk TYPE 'S3'
s3_endpoint 'https://s3.example.com'
s3_bucket 'mybucket'
s3_access_key 'your_access_key'
s3_secret_key 'your_secret_key';
-- 建库:10天一分区,保留365天,启用冷热分层
CREATE DATABASE iot_db duration 10d keep 365d tiered_storage 's3_disk';
6.2 建超级表(带缺省值、分片)
sql
CREATE STABLE meters (
ts TIMESTAMP,
current FLOAT,
voltage FLOAT DEFAULT 220 -- 不上报则自动填220
) TAGS (
location INT,
device_type BINARY(32)
) vgroups 4; -- 4核单机设4
6.3 动态写入(伪代码)
python
device_id = msg["deviceId"]
loc = msg["locationId"]
dev_type = msg["deviceType"]
current = msg.get("current")
voltage = msg.get("voltage")
columns = ["ts"]
values = ["now"]
if current is not None:
columns.append("current"); values.append(str(current))
if voltage is not None:
columns.append("voltage"); values.append(str(voltage))
sql = f"INSERT INTO {device_id} USING meters TAGS ({loc}, '{dev_type}') ({', '.join(columns)}) VALUES ({', '.join(values)})"
6.4 创建预聚合(TSMA)
sql
-- 每小时自动计算平均电流和最大电流
CREATE TSMA tsma_hourly ON meters METRICS (avg(current), max(current)) INTERVAL(1h);
-- 查询过去7天每小时平均电流,自动命中TSMA,毫秒级返回
SELECT _wstart, avg(current) FROM meters WHERE ts >= NOW - 7d INTERVAL(1h);
7. 性能基线
- 单表容量 :无硬上限,推荐控制在千万行 级。过大时配合时间裁剪 和预聚合(TSMA) 解决。
- 单行最大长度:48KB(TDengine 3.x)。
- 慢查询优化 :务必带时间条件
WHERE ts >= ...开启时间裁剪,配合 TSMA 避免全表扫描。
时间裁剪示例(假设数据库 duration 10d)
不裁剪(全表扫描)
sql
-- 无时间条件,扫描所有历史分区,几亿行数据全过一遍
SELECT avg(current) FROM d_A;
裁剪(只扫描必要分区)
sql
-- 加上时间条件,只扫描最近7天对应的分区(如6.11~6.20和6.21~6.30两个分区)
SELECT avg(current) FROM d_A WHERE ts >= NOW - 7d;
效果:扫描量从几亿行骤降到几百万行,速度提升几十倍。
时间裁剪 + TSMA 联合优化
sql
-- 查询过去30天每小时的均值
SELECT _wstart, avg(current) FROM d_A
WHERE ts >= NOW - 30d
INTERVAL(1h);
- 时间裁剪:只扫30天内的3个分区。
- TSMA 命中:直接读取预计算好的每小时汇总表,不碰原始数据。
- 结果 :从几亿行原始数据 → 几千行汇总结果,毫秒级返回。
8. 生产部署架构
设备 → MQTT Broker → Kafka(削峰) → 你的程序 → TDengine(SSD热 + S3冷)
↕ (可选) MQTT订阅 → 大屏/告警
9. 版本与资源
- TDengine OSS (开源版):免费,功能完整。
- TDengine Enterprise (企业版):收费,含 IDMP 工业平台、资产树、可视化、告警等。
- 官方文档:https://docs.taosdata.com/
- 下载地址:https://github.com/taosdata/TDengine/releases/latest