1. 背景工业与物联网海量时序数据的痛点在 CNC 数控机床、PLC 产线、传感器网关等工业数采场景中数据有一个共同的形态每条数据都带一个时间戳且按时间顺序持续产生。设备点位主轴转速、进给速度、主轴负载、温度、振动、气压、报警代码每秒甚至每毫秒上报一次一台 CNC 可能有几十到几百个点位一个车间上百台设备一天的写入量轻松达到数亿条。这带来四类传统方案难以招架的痛点写入吞吐不足关系型数据库按行存储、BTree 索引每次写入都要维护单机每秒几万行的写入对工业点位量级是杯水车薪而时序数据 90% 以上是追加写append-only需要针对批量顺序追加做专门优化。存储膨胀与过期清理困难点位数据大多越老越没用但业务又有保留窗口如保留 90 天原始数据、5 年降采样数据。传统数据库删旧数据要么 DELETE 加锁影响写入要么 DBA 手工分区维护成本极高。查询模式错配时序查询几乎总是按设备标签 时间范围的二维切片还要做降采样原始 1s 数据聚合成 1min 均值。关系型数据库的二级索引对此效率低下通用 KV如 RocksDB只提供 key-value 单点读写无法表达某时间段内某标签组所有序列的聚合。高基数标签维度一个表如果按设备 点位拆列设备数量变化就要改表结构如果塞进一张大宽表又会出现大量稀疏列。时序数据天然需要实体标签 多个测量列指标的分层模型。TDengine涛思数据GitHub 开源核心存储引擎由 C 语言实现正是围绕这些痛点设计一设备一子表table、一类设备一超级表STable、标签与指标分离、按时间戳主键排序存储、内置保留策略与时间窗口聚合。它对外提供标准 SQL类 MySQL 语法让熟悉 SQL 的工程师可以零门槛上手同时把时序场景的写入、压缩、过期清理都内化到引擎层。为什么选数据库而不是消息队列或文件因为数采链路中数据到达后需要可查询、可聚合、可告警——MQTT/Kafka 负责传输TDengine 这类时序库负责落库后可分析二者是上下游关系与既有 kafka-go / librdkafka / paho MQTT 篇的采集链路直接衔接。2. 定位差异与关系型数据库、通用 KV 及主流库对比2.1 与关系型数据库MySQL/PostgreSQL/SQLite的定位差异维度关系型数据库TDengine数据形态行式存储支持任意关联查询列式 按时间有序存储天然面向时间切片写入模型随机/批量插入事务开销时序追加写优化批量写入吞吐可达数百万条/秒单机主键任意列唯一约束强制时间戳 子表标签组合v3 支持联合主键时序语义过期数据手动 DELETE / 分区维护数据库级 keep 保留策略自动过期降采样手写 GROUP BY 时间桶性能差INTERVAL 原生窗口聚合扫描即算适合场景交易、账务、强一致性关联数据监控、遥测、点位历史、趋势分析2.2 与通用 KVRocksDB的定位差异RocksDB第 44 篇已写是嵌入式 LSM-Tree 键值库写放大低、适合海量 KV 随机/顺序写但没有 SQL、没有时间窗口聚合、没有标签维度索引。TDengine 虽然底层也吸收了 LSM/有序存储的思想但对外是完整的数据库建表、SQL、聚合、保留策略、连接器都是开箱即用的。RocksDB 是存储引擎零件TDengine 是时序数据库整机。2.3 与嵌入式分析库 DuckDB / SQLite 的对比库类型数据模型典型用途与 TDengine 关系DuckDB嵌入式 OLAP 列存关系表可直接查 CSV/Parquet单机分析、跨文件查询分析端互补TDengine 负责时序写入与在线查询DuckDB 可对导出的历史快照做深度分析SQLite嵌入式 OLTP 行存关系表 B-Tree本地配置、单机业务不适合高频时序追加与时间窗口聚合TDengine 为服务端时序库RocksDB嵌入式 KVLSMKey-Value缓存、状态存储、底层引擎无 SQL/无聚合是引擎零件而非完整 DBTDengine服务端时序数据库库 超级表 子表 标签工业/物联网时序采集与查询2.4 与 InfluxDB 的对比同类时序库选型维度InfluxDBTDengine数据模型measurement tag set field无强制 schema强 schema超级表/子表/标签/列查询语言InfluxQL / Flux标准 SQL类 MySQL学习成本低集群与生态集群版商业授权开源版支持集群3.0 起重构高基数处理tag 基数过高内存压力大标签独立索引 子表按序存储高基数友好定位监控时序老牌选手国产开源、SQL 友好、工业场景深耕核心结论TDengine 的差异点是用 SQL 表达时序语义——把设备 时间 指标三要素固化成库/超级表/子表三层模型让工业工程师用最熟悉的 SQL 完成写入、窗口聚合与保留管理。3. 核心概念与 API 说明3.1 数据模型库、超级表、子表、标签、列、时间戳主键TDengine 的数据组织是四层结构DATABASE库 └── STable超级表一类设备的模板 ├── 标签 Tag静态属性device_id / line / type可增删改 └── 子表 Table一个具体设备继承超级表结构 指定标签值 └── 列第一列必须是时间戳主键 ts其后为指标列库Database物理存储与策略容器。建库时指定保留策略 keep、时间戳精度 PRECISION、副本数 REPLICA、缓存 CACHEMODEL 等。所有表归属某个库。超级表STable一类设备的模板定义列结构时间戳 指标列与标签 schema设备维度字段。一个超级表下有多个子表。子表Table实际存储数据的表继承超级表的列结构并绑定一组具体标签值如 device_idCNC-001, lineL1。每个子表内部按时间戳有序存储这就是时序追加快的根源。标签Tag静态维度元数据建子表时指定。标签不是数据列不随每次写入变化查询时可 WHERE 过滤、GROUP BY 分组、PARTITION BY 分区。标签值修改用 ALTER TABLE ... SET TAG。列Column除首列时间戳外的数据列类型支持 BOOL / TINYINT / SMALLINT / INT / BIGINT / FLOAT / DOUBLE / VARCHAR / NCHAR / TIMESTAMP / JSONv3 支持 JSON 类型列等。时间戳主键Timestamp每张表第一列必须为时间戳精度在建库时指定ms/us/ns默认 ms。时间戳必须是严格递增或允许等值由建库参数控制见常错点。心智模型超级表 关系数据库的表结构 分区维度子表 一个具体实体的时间序列标签 关系数据库的索引列/分区键。查询时你写 SQL 操作超级表TDengine 自动路由到所有子表并合并结果。3.2 SQLCREATE DATABASE / STABLE / TABLE-- 建库keep 保留 365 天PRECISION ms 时间戳毫秒WAL 与缓存参数按需 CREATE DATABASE IF NOT EXISTS iot_db KEEP 365 DAYS 10 BLOCKS 4 CACHEMODEL both; USE iot_db; -- 建超级表cnc_metrics 含时间戳 ts、指标列 spindle_speed / spindle_load / temp CREATE STABLE IF NOT EXISTS cnc_metrics ( ts TIMESTAMP, spindle_speed FLOAT, -- 主轴转速 rpm spindle_load FLOAT, -- 主轴负载 % temp FLOAT -- 主轴温度 ℃ ) TAGS ( device_id VARCHAR(32), -- 设备编号 line VARCHAR(16), -- 产线 model VARCHAR(32) -- 设备型号 ); -- 建普通表不建超级表也可以直接建子表/普通表但不推荐丢失标签维度 CREATE TABLE IF NOT EXISTS cnc_metrics_001 ( ts TIMESTAMP, spindle_speed FLOAT, spindle_load FLOAT, temp FLOAT );要点CREATE DATABASE 的 KEEP 决定数据保留天数默认 3650 天可设 KEEP 90。CREATE STABLE 时列结构定义在前、TAGS 定义在后标签类型与列类型集合不同标签不支持 TIMESTAMP。一个库可建多个超级表超级表数量、子表数量在 v3 中大幅放宽万级超级表、亿级子表可管理。3.3 INSERT 自动建表TDengine 最常用的写法是INSERT INTO ... USING STable TAGS (...) VALUES (...) 自动建表写入时若子表不存在引擎自动按超级表模板 标签值建子表然后插入数据。这是多设备批量接入的零手工建表模式-- 自动建子表 cnc_metrics_001不存在则建标签绑定后写入一条 INSERT INTO cnc_metrics_001 USING cnc_metrics TAGS (CNC-001,L1,Mazak) VALUES (2026-09-24 08:00:00.000, 4200.0, 62.5, 41.2); -- 批量多值 INSERT INTO cnc_metrics_001 USING cnc_metrics TAGS (CNC-001,L1,Mazak) VALUES (2026-09-24 08:00:00.100, 4201.0, 62.6, 41.3), (2026-09-24 08:00:00.200, 4200.5, 62.4, 41.2); -- 多表多值一次提交推荐批量写法 INSERT INTO cnc_metrics_001 USING cnc_metrics TAGS (CNC-001,L1,Mazak) VALUES (...) cnc_metrics_002 USING cnc_metrics TAGS (CNC-002,L1,DMG) VALUES (...);3.4 SELECT 聚合与时间窗口 INTERVAL / SLIDING、GROUP BY tags时序查询两大支柱标签过滤与时间窗口聚合降采样。-- 按标签过滤 时间范围 SELECT device_id, ts, spindle_load FROM cnc_metrics WHERE line L1 AND ts 2026-09-24 08:00:00 AND ts 2026-09-24 09:00:00; -- 降采样每 5 分钟一个窗口窗口滑动 1 分钟重叠窗口 SELECT _wstart, _wend, AVG(spindle_load), MAX(spindle_load), COUNT(*) FROM cnc_metrics WHERE ts 2026-09-24 08:00:00 AND ts 2026-09-24 12:00:00 INTERVAL(5m) SLIDING(1m) GROUP BY device_id; -- 按标签分组统计不按时间窗口 SELECT device_id, COUNT(*), AVG(temp) FROM cnc_metrics WHERE ts 2026-09-24 08:00:00 AND ts 2026-09-24 09:00:00 GROUP BY device_id; -- 按多个标签维度分组 SELECT line, model, AVG(spindle_load) FROM cnc_metrics WHERE ts 2026-09-24 08:00:00 AND ts 2026-09-24 09:00:00 GROUP BY line, model;要点INTERVAL(5m) 定义窗口长度SLIDING(1m) 定义窗口推进步长默认等于 INTERVAL。_wstart / _wend 是窗口起止伪列。INTERVAL 查询中若需要按设备分别开窗必须配合 GROUP BY 标签 或 PARTITION BY否则所有子表数据会合并进同一窗口。窗口聚合对告警如最近 5 分钟平均负载 90%非常自然。3.5 保留策略 keep-- 建库时指定保留 90 天 CREATE DATABASE iot_db KEEP 90; -- 修改已有库 ALTER DATABASE iot_db KEEP 180;keep 单位是天超过保留期的数据由系统后台自动删除按 vnode 时间有序删除代价低。生产实践原始点位数据 keep 90降采样结果单独建库/表 keep 10953 年兼顾容量与追溯。3.6 REST 接口端口 6041TDengine 从 2.x 起内置taosAdapter在6041端口提供 HTTP/REST API兼容类 SQL请求体POST http://host:6041/rest/sql/dbname Authorization: Basic base64(user:pass) Body: SQL 语句字符串如 SELECT * FROM cnc_metrics LIMIT 1返回 JSON{ code: 0, desc: succ, column_meta: [[ts,TIMESTAMP,8],[spindle_speed,FLOAT,4]], data: [[2026-09-24T08:00:00.0000800, 4200.0]] }REST 适合跨语言、跨平台的轻量接入脚本、浏览器、边缘网关、无 SDK 的语言。支持 GET /rest/sql仅查询与 POST /rest/sql读写均可/rest/sqlt 返回时间戳为可读格式。默认账号 root / taosdata生产务必修改。端口 6041 与原生连接端口 6030 的区别见常错点 19。3.7 Go 连接器driver-go / taosSqlTDengine 官方 Go 连接器为 github.com/taosdata/driver-go/v3提供两层 APItaosSqldatabase/sql 驱动实现 database/sql/driver 接口可用标准 sql.Open / Query / Exec 编程SQL 语法与 TDengine 一致。taosRestful走 REST6041的 database/sql 驱动适合无原生端口6030暴露的跨网络环境。import ( database/sql _ github.com/taosdata/driver-go/v3/taosSql // 原生驱动6030 // _ github.com/taosdata/driver-go/v3/taosRestful // REST 驱动6041 ) db, err : sql.Open(taosSql, root:taosdatatcp(localhost:6030)/iot_db) if err ! nil { panic(err) } defer db.Close() // 查询 rows, err : db.Query(SELECT device_id, AVG(spindle_load) FROM cnc_metrics WHERE ts NOW - 1h INTERVAL(5m) GROUP BY device_id) // 迭代 rowsrows.Next() rows.Scan(deviceID, avgLoad)DSN 格式user:passtcp(host:port)/dbname。原生taosSql与 RESTtaosRestful二选一 import二者不能同时注册默认名。大数据量查询注意 rows.Close()批量写入建议用参数绑定taosSql 也支持 stmt。3.8 C 连接器taos_connect / taos_query / stmt 参数绑定TDengine 核心为 C 实现C 连接器即官方 libtaos#include taos.h taos_init(); TAOS *taos taos_connect(localhost, root, taosdata, iot_db, 6030); if (taos NULL) { fprintf(stderr, connect failed: %s\n, taos_errstr(taos)); return -1; } // 简单查询 TAOS_RES *res taos_query(taos, SELECT COUNT(*) FROM cnc_metrics); if (taos_field_count(res) 0) { /* 非查询语句 */ } TAOS_ROW row; while ((row taos_fetch_row(res)) ! NULL) { printf(count %s\n, (char *)row[0]); } taos_free_result(res); taos_close(taos); taos_cleanup();参数绑定stmt批量写入是 C 侧高性能推荐路径v3 提供 taos_stmt_* 系列TAOS_STMT *stmt taos_stmt_init(taos); const char *sql INSERT INTO ? USING cnc_metrics TAGS (?, ?, ?) VALUES (?, ?, ?, ?); taos_stmt_prepare(stmt, sql, (unsigned long)strlen(sql)); // 依次 bind 表名、三个 tag、四个 valuets, speed, load, temp taos_bind_param(stmt, 1, TAOS_BIND_PARAM_TBNAME, cnc_metrics_001, 0); taos_bind_param(stmt, 2, TAOS_BIND_PARAM_TAG, CNC-001, 0); /* ... 其余 tag 与 value ... */ taos_stmt_add_batch(stmt); // 每行 add_batch taos_stmt_execute(stmt); // 批量提交 taos_stmt_close(stmt);3.9 Python 连接器taospy官方 Python 连接器 taospy对标 DB-API 2.0import taospy conn taospy.connect(hostlocalhost, userroot, passwordtaosdata, databaseiot_db, port6030) cur conn.cursor() cur.execute(SELECT device_id, AVG(spindle_load) FROM cnc_metrics WHERE ts NOW - 30m INTERVAL(5m) GROUP BY device_id) for row in cur.fetchall(): print(row) cur.close() conn.close()taospy 走原生 6030另有 taospy[rest] 走 REST。与 pandas 配合cur.execute(...) 后用 taospy 提供的结果可转 DataFrame 做进一步分析衔接 NumPy 篇。4. 详细使用说明4.1 Docker 安装启动# 拉取并启动默认端口 6030 原生 / 6041 REST docker run -d --name tdengine \ -p 6030:6030 -p 6041:6041 \ -v tdengine-data:/var/lib/taos \ tdengine/tdengine:3.3.0.0 # 进入容器执行 taos CLI docker exec -it tdengine taos # 查看版本与运行状态 taos SELECT server_version(); taos SHOW DNODES;生产部署建议使用 taosdump 备份、配置 TAOS_FQDN 与 firstEp单机试用上述命令即可。防火墙需放行 6030/6041。4.2 REST 建库建表插入查询curl 实战BASEhttp://localhost:6041 AUTHroot:taosdata # 建库 curl -u $AUTH -d CREATE DATABASE IF NOT EXISTS iot_db KEEP 90 $BASE/rest/sql # 建超级表 curl -u $AUTH -d CREATE STABLE IF NOT EXISTS iot_db.cnc_metrics ( ts TIMESTAMP, spindle_speed FLOAT, spindle_load FLOAT, temp FLOAT ) TAGS (device_id VARCHAR(32), line VARCHAR(16), model VARCHAR(32)) $BASE/rest/sql # 插入自动建子表 curl -u $AUTH -d INSERT INTO iot_db.cnc_metrics_001 USING iot_db.cnc_metrics TAGS (CNC-001,L1,Mazak) VALUES (2026-09-24 08:00:00.000, 4200.0, 62.5, 41.2), (2026-09-24 08:00:00.100, 4201.0, 62.6, 41.3) $BASE/rest/sql/iot_db # 查询 curl -u $AUTH -d SELECT _wstart, AVG(spindle_load), MAX(temp) FROM iot_db.cnc_metrics WHERE ts 2026-09-24 08:00:00 AND ts 2026-09-24 12:00:00 INTERVAL(5m) GROUP BY device_id $BASE/rest/sql/iot_db返回 JSON 中 code 0 表示成功column_meta 描述列类型data 为行数组。4.3 Go 最小可运行示例文件main.gopackage main import ( database/sql fmt log _ github.com/taosdata/driver-go/v3/taosSql ) func main() { dsn : root:taosdatatcp(localhost:6030)/iot_db db, err : sql.Open(taosSql, dsn) if err ! nil { log.Fatal(err) } defer db.Close() // 建库建表幂等 mustExec(db, CREATE DATABASE IF NOT EXISTS iot_db KEEP 90) mustExec(db, CREATE STABLE IF NOT EXISTS iot_db.cnc_metrics ( ts TIMESTAMP, spindle_speed FLOAT, spindle_load FLOAT, temp FLOAT ) TAGS (device_id VARCHAR(32), line VARCHAR(16), model VARCHAR(32))) // 插入自动建子表 mustExec(db, INSERT INTO iot_db.cnc_metrics_001 USING iot_db.cnc_metrics TAGS (CNC-001,L1,Mazak) VALUES (2026-09-24 08:00:00.000, 4200.0, 62.5, 41.2), (2026-09-24 08:00:00.100, 4201.0, 62.6, 41.3), (2026-09-24 08:00:00.200, 4200.5, 62.4, 41.2)) // 窗口降采样查询 rows, err : db.Query(SELECT _wstart, device_id, AVG(spindle_load), MAX(temp) FROM iot_db.cnc_metrics WHERE ts 2026-09-24 08:00:00 AND ts 2026-09-24 08:30:00 INTERVAL(5m) GROUP BY device_id) if err ! nil { log.Fatal(err) } defer rows.Close() for rows.Next() { var wstart string var dev string var avg float64 var mx float64 if err : rows.Scan(wstart, dev, avg, mx); err ! nil { log.Fatal(err) } fmt.Printf(window%s device%s avg_load%.2f max_temp%.2f\n, wstart, dev, avg, mx) } if err : rows.Err(); err ! nil { log.Fatal(err) } } func mustExec(db *sql.DB, sqlText string) { if _, err : db.Exec(sqlText); err ! nil { log.Fatalf(exec %q: %v, sqlText, err) } }运行go mod init tdengine-demo go get github.com/taosdata/driver-go/v3v3.5.0 go run main.go4.4 C 最小可运行示例含参数绑定批量写入文件demo.c#include stdio.h #include string.h #include taos.h int main(void) { taos_init(); TAOS *taos taos_connect(localhost, root, taosdata, , 6030); if (taos NULL) { fprintf(stderr, connect failed\n); return -1; } // 建库建表幂等 taos_query(taos, CREATE DATABASE IF NOT EXISTS iot_db KEEP 90); taos_query(taos, USE iot_db); taos_query(taos, CREATE STABLE IF NOT EXISTS cnc_metrics ( ts TIMESTAMP, spindle_speed FLOAT, spindle_load FLOAT, temp FLOAT ) TAGS (device_id VARCHAR(32), line VARCHAR(16), model VARCHAR(32))); // 参数绑定批量写入INSERT INTO ? USING stb TAGS (?,?,?) VALUES (?,?,?,?) TAOS_STMT *stmt taos_stmt_init(taos); const char *sql INSERT INTO ? USING cnc_metrics TAGS (?, ?, ?) VALUES (?, ?, ?, ?); taos_stmt_prepare(stmt, sql, (unsigned long)strlen(sql)); const char *tbname cnc_metrics_001; const char *dev CNC-001; const char *line L1; const char *model Mazak; // 3 行批量时间戳毫秒float 值直接传 float* long long ts[3] {1727143200000LL, 1727143200100LL, 1727143200200LL}; float speed[3] {4200.0f, 4201.0f, 4200.5f}; float load[3] {62.5f, 62.6f, 62.4f}; float temp[3] {41.2f, 41.3f, 41.2f}; for (int i 0; i 3; i) { taos_bind_param(stmt, 1, TAOS_BIND_PARAM_TBNAME, tbname, 0); taos_bind_param(stmt, 2, TAOS_BIND_PARAM_TAG, dev, 0); taos_bind_param(stmt, 3, TAOS_BIND_PARAM_TAG, line, 0); taos_bind_param(stmt, 4, TAOS_BIND_PARAM_TAG, model, 0); taos_bind_param(stmt, 5, TAOS_BIND_PARAM_INT64, ts[i], 0); taos_bind_param(stmt, 6, TAOS_BIND_PARAM_FLOAT, speed[i], 0); taos_bind_param(stmt, 7, TAOS_BIND_PARAM_FLOAT, load[i], 0); taos_bind_param(stmt, 8, TAOS_BIND_PARAM_FLOAT, temp[i], 0); taos_stmt_add_batch(stmt); } int32_t affected 0; taos_stmt_execute(stmt); taos_stmt_affected_rows(stmt, affected); printf(batch inserted rows: %d\n, affected); // 查询验证 TAOS_RES *res taos_query(taos, SELECT device_id, COUNT(*), AVG(spindle_load) FROM cnc_metrics WHERE ts 2026-09-24 08:00:00 AND ts 2026-09-24 08:30:00 GROUP BY device_id); TAOS_ROW row; while ((row taos_fetch_row(res)) ! NULL) { printf(device%s count%s avg_load%s\n, (char *)row[0], (char *)row[1], (char *)row[2]); } taos_free_result(res); taos_stmt_close(stmt); taos_close(taos); taos_cleanup(); return 0; }编译以 Linux/Windows MinGW 为例链接 libtaos# Linux gcc demo.c -o demo -ltaos -I/usr/local/include/taos # Windows装好客户端后 gcc demo.c -o demo.exe -ltaos -IC:\TDengine\include -LC:\TDengine\driver注意taos_stmt_execute 返回 TAOS_SUCESS(0) 即成功批量行数通过 taos_stmt_affected_rows 获取。v2 旧接口 taos_stmt_bind_param_batch 在新版本仍可用但推荐 v3 的 taos_bind_param 逐行绑定 add_batch。4.5 Python 示例taospy文件demo.pyimport taospy conn taospy.connect(hostlocalhost, userroot, passwordtaosdata, databaseiot_db, port6030) cur conn.cursor() # 建库建表 cur.execute(CREATE DATABASE IF NOT EXISTS iot_db KEEP 90) cur.execute(USE iot_db) cur.execute(CREATE STABLE IF NOT EXISTS cnc_metrics ( ts TIMESTAMP, spindle_speed FLOAT, spindle_load FLOAT, temp FLOAT ) TAGS (device_id VARCHAR(32), line VARCHAR(16), model VARCHAR(32))) # 插入 cur.execute(INSERT INTO cnc_metrics_001 USING cnc_metrics TAGS (CNC-001,L1,Mazak) VALUES (2026-09-24 08:00:00.000, 4200.0, 62.5, 41.2), (2026-09-24 08:00:00.100, 4201.0, 62.6, 41.3), (2026-09-24 08:00:00.200, 4200.5, 62.4, 41.2)) # 窗口降采样 cur.execute(SELECT _wstart, device_id, AVG(spindle_load), MAX(temp) FROM cnc_metrics WHERE ts 2026-09-24 08:00:00 AND ts 2026-09-24 08:30:00 INTERVAL(5m) GROUP BY device_id) for row in cur.fetchall(): print(row) cur.close() conn.close()pip install taospy python demo.py4.6 贴合场景CNC/PLC 设备点位历史数据、趋势降采样与告警以一条CNC 车间点位采集 → 边缘网关 → TDengine → 趋势/告警链路为例点位模型设计一类设备一个超级表点位指标做列设备维度做标签。不要一个点位一张表否则子表数量爆炸见常错点 8。-- PLC 数字量/模拟量统一入一个超级表按设备子表隔离 CREATE STABLE IF NOT EXISTS plc_points ( ts TIMESTAMP, temp FLOAT, -- 温度模拟量 pressure FLOAT, -- 压力模拟量 status BOOL, -- 运行状态数字量 alarm_code INT -- 报警代码 ) TAGS (device_id VARCHAR(32), line VARCHAR(16), station VARCHAR(16));历史数据查询设备点位回放SELECT ts, temp, pressure, status FROM plc_points WHERE device_id PLC-01 AND ts 2026-09-23 00:00:00 AND ts 2026-09-24 00:00:00 ORDER BY ts ASC;趋势降采样1s 原始 → 1min 均值用于大屏趋势图SELECT _wstart, AVG(temp) AS avg_temp, MAX(temp) AS max_temp FROM plc_points WHERE device_id PLC-01 AND ts 2026-09-24 08:00:00 AND ts 2026-09-24 09:00:00 INTERVAL(1m);告警规则最近 5 分钟平均负载超阈值SELECT device_id, AVG(spindle_load) AS avg_load FROM cnc_metrics WHERE ts NOW - 5m INTERVAL(5m) GROUP BY device_id HAVING avg_load 90;落库后衔接分析TDengine 在线查询 taosdump/导出 CSV 到 DuckDB 做深度分析告警结果回流 Kafka。5. 常错点 / 坑20 条坑点说明与规避1时间戳精度与时区建库时 PRECISION ms 决定全库时间戳粒度ms/us/ns改精度需重建库客户端传入字符串带时区08:00时按本地时区解析无时区字符串按数据库 timezone 配置解析。建议统一用毫秒时间戳 UTC 存储、展示时转本地避免跨时区部署混乱。2标签与列混淆标签是静态维度WHERE 过滤、GROUP BY 分组列是随时间变化的测量值。把动态指标建为标签会导致标签被频繁修改性能差且语义错误把设备维度建成列则每个新设备都要改表结构。原则变化的做列不变的做标签。3自动建表语法写错正确形态 INSERT INTO 子表名 USING 超级表 TAGS (tag值...) VALUES (...)。常见错误忘记 USING、TAGS 与 VALUES 数量/顺序不匹配、先建普通表再 INSERT 同名子表导致结构冲突。4重复时间戳被忽略/报错默认同一子表时间戳必须严格递增或允许等值取决于建库参数 TIMESTAMP 精度与 v3 的 ... 配置重复时间戳写入会被忽略或返回错误。采集端去重/重试逻辑必须处理同 ts 同值重发推荐在应用侧用幂等键或忽略重复。5逐条 INSERT 性能差每条 SQL 一条 VALUES 是最大性能杀手。必须批量多 VALUES / 多子表一次提交或用 stmt 参数绑定TDengine 的写入吞吐依赖批量追加单条写入可能只有几千行/秒批量可达百万行/秒。6表数量膨胀与 tags 设计每个设备一个子表若把点位建表如 table_per_point设备 × 点位会指数膨胀。正确做法设备子表、点位列标签基数过高如把随机值/时间戳做标签会导致元数据膨胀标签应是低基数的稳定维度。7keep 保留策略误用KEEP 0 表示永久保留不推荐修改 KEEP 只影响新建/后续清理不立即删除已有超期数据后台周期性清理。降采样结果要单独建表并设更长 keep否则原始数据过期时降采样也一起没了。8REST 返回格式解析REST 返回 {code, desc, column_meta, data}code ! 0 即失败column_meta 与 data 一一对应。常见错误直接取 data 不查 code把毫秒时间戳当秒字符串列在 JSON 里是字符串但数值列是 number。9taosRestful vs taosSql 混用driver-go 中 taosSql 走原生 6030高性能、支持 stmttaosRestful 走 REST 6041跨网络友好但少部分特性受限。二者驱动名不同不能同时 _ ... 注册同一别名按部署拓扑选其一。10NaN/Null 聚合语义FLOAT/DOUBLE 列允许 NaN聚合函数AVG/SUM/COUNT对 NaN 与 NULL 的计数语义不同COUNT(*) 计数含 NULL 行、COUNT(col) 只计非 NULL。查询前先确认是否需要 IS NOT NULL 过滤避免均值被 NaN 污染。11v2/v3 差异v3 重构了集群架构与存储vnode/tsdb 演进语法大体兼容但v3 中 CREATE DATABASE 参数如 CACHEMODEL、WAL_RETENTION_PERIOD、taos_stmt_* 绑定接口、部分系统表information_schema均有变化。按 3.x 文档写新代码迁移旧脚本先跑 taos -s SELECT server_version() 确认版本。12端口 6030 与 6041 混淆6030 是原生 taosc 连接端口C/Go/Python 原生驱动6041 是 REST/taosAdapter 端口。防火墙只开 6041 时原生驱动连不上SDK 客户端配置 port6030 却走 REST 驱动也会失败。13Windows 连接问题Windows 上原生驱动需安装 TDengine Windows 客户端提供 taos.dll/taosc 库或直接用 REST 6041curl/taosRestful/HTTP SDK免装客户端。另外 Windows 下 taos.exe CLI 默认读 C:\TDengine\cfg\taos.cfgfirstEp 配错会连不上。14忘记 USE 库/表名不带库前缀连接器 DSN 指定了 database 时自动 USE但 REST 的 /rest/sql/db 与 /rest/sql无库行为不同SQL 中未加库前缀会报 Table does not exist。建议 SQL 一律写 库名.表名 或显式 USE。15INTERVAL 不配 GROUP BY 标签多设备数据在一个超级表里INTERVAL 聚合不写 GROUP BY device_id 时全部设备混在一个窗口序列趋势图完全错乱。窗口聚合必须显式按标签分组。16字符串类型 VARCHAR 长度与 NCHARVARCHAR 按字节计、NCHAR 按字符计中文字段建议 NCHAR且注意 VARCHAR(n) 长度不足导致插入截断/报错。标签/列名长度、字符集utf8在不同版本有差异跨版本先查文档。17ORDER BY 时序查询性能陷阱时间有序是引擎优势按 ts 范围查询无需全表扫描但 ORDER BY 非时间列、JOIN 子表间关联、无时间过滤的聚合可能触发全扫描。查询务必带时间范围下界。18未做写入失败重试与幂等网络抖动丢一批写入若直接重放整批可能引入重复时间戳/重复数据配合采集端缓冲Kafka/MQTT 消费 offset实现 at-least-once 应用侧去重避免数据空洞或重复。19默认账号未改/安全裸奔默认 root/taosdata、REST 走 Basic 明文生产必须改密码、限制 IP、必要时走 HTTPS/内网隔离。20备份缺失TDengine 没有像 MySQL binlog 一样默认全量备份用 taosdump 定期备份库/表配合 keep 策略规划恢复窗口。6. 总结6.1 适用场景表场景是否推荐 TDengine说明工业 CNC/PLC 点位历史数据采集与回放✅ 强推设备子表、点位列、标签维度时间窗口查询天然匹配物联网传感器遥测温湿度/电量/GPS✅ 强推高写入吞吐 自动建表 keep 过期监控指标服务器 CPU/内存、业务 QPS✅ 推荐对标 Prometheus/InfluxDBSQL 更通用交易/账务/强一致关系数据❌ 不适用用 MySQL/PostgreSQL单机嵌入式小规模存储⚠️ 看情况数据量小且要离线分析可用 SQLite/DuckDB通用 KV 缓存/状态存储❌ 不适用用 Redis/RocksDB跨文件/Parquet 深度分析⚠️ 分析端导出后交给 DuckDB 等 OLAP6.2 工业数采链路建议CNC/PLC 设备 └─(Modbus RTU/TCP / OPC UA)─→ 边缘采集网关C/Go 采集器 └─(MQTT QoS1 / Kafka)─→ 消息总线 └─(消费落库)─→ TDengineiot_db原始点位 keep 90降采样 keep 1095 ├─ REST/SQL 在线查询 → 大屏趋势 / 历史回放 ├─ INTERVAL 窗口聚合 → 告警规则HAVING 阈值 └─ taosdump/CSV 导出 → DuckDB 深度分析 / PyTorchONNX 预测质检子表命名规范cnc_metrics_001设备唯一标签必含产线/型号等分组维度。原始数据与降采样分离建库不同 keep避免降采样被原始策略误删。告警尽量用窗口聚合5m 均值而非单点阈值减少抖动误报。6.3 FAQ 速查表问题答案TDengine 是什么涛思数据开源的时序数据库核心 C 语言实现SQL 友好面向 IoT/工业海量时序数据。端口 6030 / 6041 分别是什么6030 原生连接C/Go/Python 原生驱动6041 RESTtaosAdapter。超级表和子表什么关系超级表是一类设备的模板列标签 schema子表是具体设备实例继承结构绑定标签值数据按子表时间有序存储。标签和列的区别标签是静态维度过滤/分组列是动态测量值变化的做列、不变的做标签。如何自动建表INSERT INTO 子表 USING 超级表 TAGS (...) VALUES (...)表不存在自动创建。怎么做降采样SELECT ... INTERVAL(5m) SLIDING(1m) GROUP BY device_id配合 _wstart/_wend 伪列。如何设置数据保留时间建库 CREATE DATABASE ... KEEP 90天ALTER DATABASE ... KEEP 180 可改。Go 怎么连sql.Open(taosSql, root:taosdatatcp(localhost:6030)/db)REST 用 taosRestful 驱动 6041。C 怎么批量写stmt 参数绑定taos_stmt_prepare taos_bind_param taos_stmt_add_batch taos_stmt_execute。Python 怎么连pip install taospytaospy.connect(host, user, password, database, port6030)。时间戳精度怎么选建库 PRECISION ms全库统一改精度需重建库跨时区建议 UTC 毫秒。写入慢怎么排查看是否逐条 INSERT改用批量 VALUES/stmt检查网络往返、WAL 参数、是否触发了重复时间戳丢弃。表太多怎么办避免点位建表设备子表、点位列标签保持低基数。与 InfluxDB 比有什么优势SQL 标准、标签/子表模型直观、国产开源社区活跃InfluxDB 生态更老牌选型看团队栈。支持 JSON 数据吗v3 支持 JSON 类型列可存半结构化点位附加信息。Windows 上怎么用装 Windows 客户端后用原生驱动或直接 REST 6041 免客户端。需要备份吗需要用 taosdump 定期导出keep 只管过期删除不管容灾。本文代码基于 TDengine 3.x3.3.x语法如使用 2.x 请注意 taos_stmt_*、建库参数与系统表差异见常错点 11。文中示例均可在本地 Docker 部署后直接运行验证。内容由AI生成仅供参考
