news 2026/9/13 14:49:35

TDengine Schemaless 无模式写入指南:行协议、自动建表规则与多语言实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TDengine Schemaless 无模式写入指南:行协议、自动建表规则与多语言实战

TDengine Schemaless 无模式写入指南:行协议、自动建表规则与多语言实战

【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine

导读

在工业物联网(IIoT)场景中,设备采集项往往随应用逻辑升级、硬件调整而频繁变化,若每次都手工建表将极大拖慢数据接入节奏。TDengine 提供了 Schemaless(无模式)写入方式:无需预先创建超级表与子表,写入时自动完成建表、加列与类型推断,同时保持与 InfluxDB Line Protocol、OpenTSDB Telnet / JSON 协议的兼容。本文基于 TDengine 开源仓库的开发者指南文档(docs/en/10-developer-guide/04-schemaless.md)与对应源码、示例,系统讲解行协议语法、自动建表命名规则、配置项、时间精度识别、数据模式映射与变化处理,并给出 WebSocket 与 Native 两种连接的 Java/Python/Go/Rust/Node.js/C#/C 多语言可运行示例,帮助你快速上手并规避常见写入错误。

Schemaless 写入机制概述

在 IoT 应用中,为达成自动化管理、业务分析、设备监控等功能,往往需要采集大量数据项。由于应用逻辑升级、设备硬件调整等原因,采集项可能频繁变化。TDengine 的 Schemaless 写入方法正是为简化数据记录流程而设计。

其核心机制包含三点:

  1. 自动建表:用户无需预先创建超级表(supertable)或子表(subtable),TDengine 会根据实际写入的数据自动创建对应的存储结构。
  2. 自动加列:必要时自动为已存在的表补充缺失的数据列(普通列)或标签列(tag),确保用户写入的数据被正确存储。
  3. 与 SQL 建表等价:通过 Schemaless 写入创建的超级表及其子表,与直接通过 SQL 创建的表在功能上没有任何差异,仍可直接使用 SQL 写入数据。唯一的区别在于:由 Schemaless 自动生成的子表名基于标签值按固定映射规则生成,可读性较差、不易直接理解。

重要提示:使用 Schemaless 写入时表由系统自动创建,手动创建同名表可能导致未知错误;同理,文档也不推荐手动预建超级表,否则数据插入可能异常。

Schemaless 写入行协议

TDengine 的 Schemaless 行协议兼容三类协议:

  • InfluxDB 行协议(Line Protocol)
  • OpenTSDB Telnet 行协议
  • OpenTSDB JSON 格式协议

其中 InfluxDB 与 OpenTSDB 的标准协议写法请参考其各自官方文档。下面重点介绍 TDengine 在 InfluxDB 行协议基础上扩展出的协议内容,它允许用户更精细地控制超级表 schema。每一行数据用一条字符串表达,多条行字符串可一次性传入写入 API 实现批量写入,格式为:

measurement,tag_set field_set timestamp

各组成部分说明如下:

组成部分格式说明
measurement表名tag_set之间用逗号分隔
tag_set<tag_key>=<tag_value>,<tag_key>=<tag_value>标签列数据,各项用逗号分隔,与field_set之间用空格分隔
field_set<field_key>=<field_value>,<field_key>=<field_value>普通列数据,各项用逗号分隔,与timestamp之间用空格分隔
timestamp时间戳该行数据的主键时间戳

注意:Schemaless 写入不支持向带第二复合主键列的表写入数据。

tag_set中的所有数据都会被自动转换为nchar数据类型,无需使用双引号。

数据项类型标注(field_set 类型描述)

在 Schemaless 行协议中,field_set中的每个数据项都需要描述自身的数据类型,规则如下:

写法类型示例
双引号包裹varchar(Binary)"abc"
双引号包裹且前缀LlncharL" error message "
双引号包裹且前缀GggeometryG"Point(4.343 89.342)"
双引号包裹且前缀BbvarbinaryB"\x98f46e"B"hello"(双引号内可含以\x开头的十六进制,也可为普通字符串)

转义规则

对于空格、等号(=)、逗号(,)、双引号(")、反斜杠(\),都需要使用反斜杠进行转义(全部为半角英文符号)。各字段域(domain)的转义规则如下:

编号字段需转义的字符
1超级表名逗号、空格
2标签名逗号、等号、空格
3标签值逗号、等号、空格
4列名逗号、等号、空格
5列值双引号、反斜杠

反斜杠自身的转义遵循"连续反斜杠"规则:若连续出现两个反斜杠,第一个作为转义字符;若只有一个反斜杠,则无需转义。具体映射如下:

编号反斜杠(原始)转义结果
1\\
2\\\
3\\\\\
4\\\\\\
5\\\+\\(五个\\\\
6\\\\+\\(六个\\\\

数值类型后缀映射

数值类型通过后缀区分,映射规则如下:

编号后缀映射类型字节数
1无后缀或f64double8
2f32float4
3i8/u8TinyInt / UTinyInt1
4i16/u16SmallInt / USmallInt2
5i32/u32Int / UInt4
6i64/i/u64/uBigInt / BigInt / UBigInt / UBigInt8

此外,tTtrueTrueTRUEfFfalseFalse会被直接识别为BOOL类型。

完整示例

下面这行数据表示:在名为st的超级表下,以标签t1="3"(NCHAR)、t2="4"(NCHAR)、t3="t3"(NCHAR)定位子表,写入一行数据:列c1=3(BIGINT)、c2=false(BOOL)、c3="passit"(BINARY)、c4=4(DOUBLE),主键时间戳为1626006833639000000

st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4f64 1626006833639000000

注意:如果数据类型后缀描述有误(如大小写错误),或为数据指定的类型本身不正确,可能触发错误信息并导致写入失败。

幂等性与原子性

TDengine 为数据写入提供了幂等性:可以重复调用 API 写入之前写入失败的数据,不会产生重复副作用。但不提供多行数据的原子性:批量写入多行数据时,可能出现部分行成功、部分行失败的情况,需要应用侧根据返回错误码做补偿处理。

Schemaless 写入处理规则

Schemaless 写入按以下 12 条原则处理行数据:

  1. 子表名生成规则:先将 measurement 名称与标签的 key、value 拼接为如下字符串:

    "measurement,tag_key1=tag_value1,tag_key2=tag_value2"
    • 注意tag_key1tag_key2并非用户输入时的原始顺序,而是按标签名升序排序后的结果,因此tag_key1不一定是行协议中第一个输入的标签。
    • 排序后对该字符串计算 MD5 哈希值md5_val,再将计算结果与字符串拼接生成表名:t_md5_valt_是固定前缀,所有通过该映射自动生成的表都带此前缀。
    • 如果不想使用自动生成的表名,有两种方式指定子表名(方式一优先级更高):
      1. taos.cfg中配置smlAutoChildTableNameDelimiter(取值不能包含@ # 空格 CR LF 制表符)。例如配置smlAutoChildTableNameDelimiter=-后,写入st,t0=cpu1,t1=4 c1=3 1626006833639000000,创建的表名为cpu1-4
      2. taos.cfg中配置smlChildTableName。例如配置smlChildTableName=tname后,写入st,tname=cpu1,t1=4 c1=3 1626006833639000000,创建的表名为cpu1。注意:如果多行数据具有相同的tnametag_set不同,则以首次自动建表时指定的 tag_set 为准,其余行会被忽略。
  2. 若解析行协议得到的超级表不存在,则自动创建(不推荐手动创建超级表,否则数据插入可能异常)。

  3. 若解析得到的子表不存在,则按第 1 步确定的子表名创建子表。

  4. 若数据行中指定的标签列或普通列不存在,会将其添加到超级表中(只增不减)。

  5. 若超级表中已存在某些标签列或普通列,但某数据行未指定它们,则该行中这些列的值为NULL

  6. 对于 BINARY 或 NCHAR 列,若数据行提供的值长度超过列类型上限,会自动扩大该列的最大字符存储上限(只增不减),确保数据完整存储。

  7. 整个处理过程中遇到的错误会中断写入流程并返回错误码。

  8. 为提高写入效率,默认假设同一超级表内field_set的列顺序一致(首条数据包含全部字段,后续数据沿用该顺序)。若顺序不同,需配置smlDataFormatfalse,否则数据会按相同顺序写入,导致库内数据错乱。自3.0.3.0版本起系统会自动检查顺序一致性,该配置已废弃。

  9. 由于 SQL 建表不支持点号(.),Schemaless 会将自动创建的表名中的点号替换为下划线(_)。若手工指定子表名且包含点号,也会被转换为下划线。

  10. taos.cfg新增smlTsDefaultName配置(值为字符串),仅作用于客户端。配置后可通过它设置 Schemaless 自动建表的时间列名,未配置时默认为_ts

  11. Schemaless 写入中的超级表名、子表名区分大小写

  12. Schemaless 写入仍受 TDengine 底层数据结构限制:每行数据总长度不能超过 48KB(自3.0.5.0版本起为 64KB),标签值总长度不能超过 16KB。

配置项在源码中的注册位置

上述smlChildTableNamesmlAutoChildTableNameDelimitersmlTagNamesmlTsDefaultNamesmlDot2Underline等配置项均作为客户端本地(CFG_SCOPE_CLIENT / CFG_CATEGORY_LOCAL)动态配置在 source/common/src/tglobal.c 中注册,且支持运行时动态调整(CFG_DYN_CLIENT),可在taos.cfg中集中管理,也可以在连接建立前通过客户端 API 设置。

时间分辨率识别

Schemaless 写入支持三种指定模式:

编号描述
1SML_LINE_PROTOCOLInfluxDB Line Protocol
2SML_TELNET_PROTOCOLOpenTSDB Text Line Protocol
3SML_JSON_PROTOCOLJSON 格式协议

SML_LINE_PROTOCOL解析模式下,用户需要显式指定输入时间戳的时间分辨率,可选值如下:

编号时间分辨率定义含义
1TSDB_SML_TIMESTAMP_NOT_CONFIGURED未定义(非法)
2TSDB_SML_TIMESTAMP_HOURS小时
3TSDB_SML_TIMESTAMP_MINUTES分钟
4TSDB_SML_TIMESTAMP_SECONDS
5TSDB_SML_TIMESTAMP_MILLI_SECONDS毫秒
6TSDB_SML_TIMESTAMP_MICRO_SECONDS微秒
7TSDB_SML_TIMESTAMP_NANO_SECONDS纳秒

SML_TELNET_PROTOCOLSML_JSON_PROTOCOL模式下,时间精度由时间戳的长度决定(与 OpenTSDB 标准行为一致),用户指定的时间分辨率将被忽略。

数据模式映射规则

InfluxDB 行协议数据会被映射为带 schema 的表结构,映射关系为:

  • measurement→ 超级表名
  • tag_set中的标签名 → schema 中的标签名
  • field_set中的字段名 → 普通列名

以如下数据为例:

st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4f64 1626006833639000000

该行数据映射创建超级表st,包含 3 个 nchar 类型标签t1t2t3,以及 5 个数据列:ts(timestamp)、c1(bigint)、c3(binary)、c2(bool)、c4(bigint),等价于如下 SQL:

create stable st (_ts timestamp, c1 bigint, c2 bool, c3 binary(6), c4 bigint) tags(t1 nchar(1), t2 nchar(1), t3 nchar(2))

可见:数值后缀直接决定列类型(如3i64映射为 bigint),双引号字符串按长度映射为binary(n),标签值按长度映射为nchar(n)

数据模式变化处理

本节说明不同行数据写入场景对数据 schema 的影响。

场景一:显式类型标识变更 → 报错

使用行协议写入带明确类型标识的字段时,后续若改变该字段的类型定义,将触发明确的数据 schema 错误,写入 API 返回错误。例如:

st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4 1626006833639000000 st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4i 1626006833640000000

第一行将c4定义为 Double,第二行却通过数值后缀将同一列声明为 BigInt,从而触发 Schemaless 解析错误。

场景二:BINARY 列长度扩展 → 自动加宽

若前面行将某数据列声明为 binary,后续行需要更长的二进制长度,将触发超级表 schema 变更(自动加宽):

st,t1=3,t2=4,t3=t3 c1=3i64,c5="pass" 1626006833639000000 st,t1=3,t2=4,t3=t3 c1=3i64,c5="passit" 1626006833640000000

第一行声明列c5binary(4);第二行写入时识别到c5仍为 binary 列但宽度为 6,此时自动将列宽扩大以容纳新字符串(binary(6))。

场景三:新增列 → 自动加列

st,t1=3,t2=4,t3=t3 c1=3i64 1626006833639000000 st,t1=3,t2=4,t3=t3 c1=3i64,c6="passit" 1626006833640000000

第二行相对第一行新增了列c6,类型为binary(6),系统将自动为超级表添加c6(binary(6))列。

Schemaless 写入示例(智能电表场景)

下面以智能电表为例,介绍使用各类语言连接器通过 Schemaless 写入接口写数据的代码示例,覆盖 InfluxDB Line Protocol、OpenTSDB Telnet 与 OpenTSDB JSON 三种协议。

运行前提

  • 由于 Schemaless 自动建表规则与 SQL 示例不同,运行前请确保metersmetric_telnetmetric_json等表不存在
  • OpenTSDB Telnet 行协议与 OpenTSDB JSON 格式协议仅支持一个数据列,因此示例采用了其他数据组合。

WebSocket 连接方式

WebSocket 连接通过 REST/WebSocket 网关(默认端口 6041)写入,典型示例文件如下:

  • Java:执行带reqId的 Schemaless 写入,最后一个参数reqId可用于请求链路追踪:
    writer.write(lineDemo, SchemalessProtocolType.LINE, SchemalessTimestampType.NANO_SECONDS, 1L);

    完整代码参见 docs/examples/JDBC/JDBCDemo/src/main/java/com/taos/example/SchemalessWsTest.java。

  • Python(docs/examples/python/schemaless_ws.py):先创建数据库power,再通过conn.schemaless_insert(...)分别以 Line、Telnet、Json 三种协议写入:
    import taosws host = "localhost" port = 6041 lineDemo = [ "meters,groupid=2,location=California.SanFrancisco current=10.3000002f64,voltage=219i32,phase=0.31f64 1626006833639" ] telnetDemo = ["metric_telnet 1707095283260 4 host=host0 interface=eth0"] jsonDemo = [ '{"metric": "metric_json","timestamp": 1626846400,"value": 10.3, "tags": {"groupid": 2, "location": "California.SanFrancisco", "id": "d1001"}}' ] conn = taosws.connect(user="root", password="taosdata", host=host, port=port, database='power') conn.schemaless_insert( lines=lineDemo, protocol=taosws.PySchemalessProtocol.Line, precision=taosws.PySchemalessPrecision.Millisecond, ttl=1, req_id=1, ) conn.schemaless_insert( lines=telnetDemo, protocol=taosws.PySchemalessProtocol.Telnet, precision=taosws.PySchemalessPrecision.Microsecond, ttl=1, req_id=2, ) conn.schemaless_insert( lines=jsonDemo, protocol=taosws.PySchemalessProtocol.Json, precision=taosws.PySchemalessPrecision.Millisecond, ttl=1, req_id=3, )

    可以看到,WebSocket 版 API 还支持ttl(数据生命周期)与req_id(链路追踪)参数。

  • Go:推荐使用ws/unifiedSchemaless 接口(自v3.8.0起,见 docs/examples/go/schemaless/unified/main.go);ws/schemaless兼容接口(docs/examples/go/schemaless/ws/main.go)自v3.8.0起标记为废弃,暂时仍可用,建议迁移。
  • Rust(docs/examples/rust/restexample/examples/schemaless.rs)、Node.js(docs/examples/node/websocketexample/line_example.js)、C#(docs/examples/csharp/wssml/Program.cs)、C(docs/examples/c-ws-new/sml_insert_demo.c)均有对应示例。
  • REST API:不支持Schemaless 写入。

Native 连接方式

Native 连接使用 TCP 原生协议(默认端口 6030),示例文件如下:

  • Java:同样支持带reqId的写入,参见 docs/examples/JDBC/JDBCDemo/src/main/java/com/taos/example/SchemalessJniTest.java。
  • Python(docs/examples/python/schemaless_native.py):
    import taos lineDemo = [ "meters,groupid=2,location=California.SanFrancisco current=10.3000002f64,voltage=219i32,phase=0.31f64 1626006833639" ] telnetDemo = ["metric_telnet 1707095283260 4 host=host0 interface=eth0"] jsonDemo = [ '{"metric": "metric_json","timestamp": 1626846400,"value": 10.3, "tags": {"groupid": 2, "location": "California.SanFrancisco", "id": "d1001"}}' ] conn = taos.connect(user="root", password="taosdata", host="localhost", port=6030) conn.execute("CREATE DATABASE IF NOT EXISTS power") conn.select_db("power") conn.schemaless_insert( lineDemo, taos.SmlProtocol.LINE_PROTOCOL, taos.SmlPrecision.MILLI_SECONDS ) conn.schemaless_insert( telnetDemo, taos.SmlProtocol.TELNET_PROTOCOL, taos.SmlPrecision.MICRO_SECONDS ) conn.schemaless_insert( jsonDemo, taos.SmlProtocol.JSON_PROTOCOL, taos.SmlPrecision.MILLI_SECONDS )

    Native 版通过taos.SmlProtocol.LINE_PROTOCOL / TELNET_PROTOCOL / JSON_PROTOCOLtaos.SmlPrecision.*指定协议与时间精度。

  • Go(docs/examples/go/schemaless/native/main.go)、Rust(docs/examples/rust/nativeexample/examples/schemaless.rs)、C#(docs/examples/csharp/nativesml/Program.cs)、C(docs/examples/c/schemaless.c)均有对应示例。
  • Node.js 与 REST API:不支持Native Schemaless 写入。

查询写入的数据

运行上述示例后,power数据库中会自动创建相应表。可使用 TDengine CLI 或应用程序查询验证写入结果,例如:

taos> show power.stables; stable_name | ================================= meter_current | stb0_0 | meters | Query OK, 3 row(s) in set (0.002527s) taos> select * from power.meters limit 1 \G; *************************** 1.row *************************** _ts: 2021-07-11 20:33:53.639 current: 10.300000199999999 voltage: 219 phase: 0.310000000000000 groupid: 2 location: California.SanFrancisco Query OK, 1 row(s) in set (0.004501s)

可以看到:meters超级表已按智能电表场景自动建表,标签groupidlocation与普通列currentvoltagephase均被正确存储,时间列默认为_ts。你既可以用 CLI 交互查询,也可以在应用中通过 SQL 直接读写这些自动创建的表。

实践建议与注意事项

综合文档与源码,使用 Schemaless 写入时建议遵循以下实践:

  1. 不要手动预建表:让系统自动建表,避免与自动命名规则冲突引发未知错误。
  2. 保持同一超级表内 field_set 顺序一致:虽然 3.0.3.0 之后系统会自动校验列顺序,但保持一致的字段顺序仍是提升解析与写入效率的好习惯。
  3. 正确选择时间精度:Line 协议必须显式指定时间分辨率(毫秒/微秒/纳秒等);Telnet 与 JSON 协议按时间戳长度推断精度,注意与写入数据匹配。
  4. 善用子表名定制:若自动生成的t_<md5>表名不利于后续 SQL 检索,可通过smlAutoChildTableNameDelimitersmlChildTableName两个客户端配置指定可读的子表名。
  5. 留意底层限制:单行数据总长度不超过 64KB(3.0.5.0 起),标签值总长度不超过 16KB;BINARY/NCHAR 列超长时会自动加宽(只增不减),但列类型冲突(如 Double 变 BigInt)会直接报错。
  6. 充分利用幂等性:写入失败后可重复调用 API 重试;但多行批量写入不具备原子性,需按返回错误码做局部补偿。

通过本文的协议语法、自动建表规则与多语言示例,你可以将任意 IIoT 设备的动态采集项以最小改造成本接入 TDengine,并依靠 SQL 继续完成后续的查询、订阅与流式计算。

【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 14:48:54

医学图像分割实战:基于PyTorch的U-Net完整实现与调优

简介&#xff1a;基于Python与深度学习实现的医学图像分割系统&#xff0c;面向计算机、人工智能、自动化等专业的学生、教师及从业者&#xff0c;适用于毕业设计、课程设计、项目进阶练习。项目以U-Net为分割核心&#xff0c;覆盖数据预处理、模型训练、评估与预测等完整流程&…

作者头像 李华
网站建设 2026/9/13 14:47:45

Python烟花代码:用Pygame实现跨年真实感粒子特效

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 14:47:29

离线安装libpcap实战指南:依赖解析与常见坑位避坑手册

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 14:44:54

Git Worktree 实战:让 AI Coding Agent 并行开发不再互相踩踏

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 14:44:49

示波器八大底层逻辑问题:从信号观测到工程决策

1. 为什么这八个问题不是“入门题”&#xff0c;而是示波器使用逻辑的底层开关刚拿到示波器时&#xff0c;我拆开包装、接上探头、按下电源——屏幕亮了&#xff0c;波形跳出来了。但接下来整整三天&#xff0c;我都在反复做同一件事&#xff1a;调亮一点、再调暗一点&#xff…

作者头像 李华