TDengine STMT 参数绑定 — 高性能批量写入与查询的最佳方式

分类 :5.写入路径 | 篇章:03 STMT 参数绑定

适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-06-24

STMT(Prepared Statement)参数绑定是 TDengine 性能最高的写入方式。SQL 一次预编译、参数多次绑定,避免反复解析;多表交织写入更可单连接达到百万级 RPS。

核心概念速查表

概念 说明
Prepare 预编译 SQL(一次)
Bind 绑定参数(多次)
Execute 执行绑定的数据
Multi-Bind 一次绑定多行
Interlace 多子表交织绑定
STMT1 / STMT2 两代 API(STMT2 更优)

详细解析

1. STMT 工作模式

复制代码
普通 SQL 写入:
  for each batch:
    构造 SQL 字符串 → Parser → Plan → Execute → 返回
    [每次都解析 SQL]
    
STMT 写入:
  Prepare("INSERT INTO ? VALUES (?, ?, ?)")
    → Parser → Plan
    → 缓存执行计划
    
  for each batch:
    Bind(子表名, 时间数组, 值数组)
    Execute()
    [复用执行计划,仅传参数]


优势:
  - SQL 解析仅一次
  - 参数二进制传输(无文本编码)
  - 内存连续布局(列式绑定)
  - 网络包紧凑

2. STMT 操作序列

复制代码
典型操作流程:

  conn = connect(...)
  stmt = stmt2_init(conn, options)
  
  // 1. Prepare 一次
  stmt2_prepare(stmt, "INSERT INTO ? USING meters TAGS(?,?) VALUES(?,?,?,?)")
  
  // 2. 循环 Bind + Execute
  loop:
    stmt2_bind_param(stmt, bindData)
    stmt2_exec(stmt, &affected_rows)
  
  // 3. 释放
  stmt2_close(stmt)


bindData 结构:
  - 子表名(自动建表场景)
  - Tag 值数组
  - 列名数组
  - 列值数组(按列存储)
  - 行数

3. 列式绑定

复制代码
列式绑定示例:

  插入 1000 行,4 列(ts, current, voltage, phase):
  
  传统行式:
    [ts1, c1, v1, p1, ts2, c2, v2, p2, ...]
    → 内存非连续,缓存不友好
    
  STMT 列式:
    ts:     [ts1, ts2, ..., ts1000]      ← 连续 8KB
    current:[c1, c2, ..., c1000]         ← 连续 8KB
    voltage:[v1, v2, ..., v1000]         ← 连续 4KB
    phase:  [p1, p2, ..., p1000]         ← 连续 4KB
    
  优势:
    - SIMD 向量化处理
    - 内存连续高速复制
    - 服务端直接写入列式存储

4. 多表交织绑定(STMT2 核心)

复制代码
传统场景:每张子表分别 Prepare/Bind/Execute
  → 1000 个子表 → 1000 次往返
  
STMT2 交织绑定:
  Prepare("INSERT INTO ? USING meters TAGS(?,?) VALUES(?,?,?,?)")
  
  单次 Bind 包含多个子表的数据:
    sub_tables: [
      {tbname: "d001", tags: [...], values: [行1,行2,...]},
      {tbname: "d002", tags: [...], values: [行1,行2,...]},
      ...
      {tbname: "d100", tags: [...], values: [行1,行2,...]},
    ]
  
  Execute() → 一次执行写入 100 个子表 1000 行
  
  → 服务端按 VGroup 拆分并行处理

5. NULL 与空值处理

复制代码
NULL 的表示:

  每列有 is_null 数组(同长度):
    values:  [25.3, 25.5, NULL, 25.7]
    is_null: [0,    0,    1,    0]
  
  Bind 时:
    is_null[i] = 1 → 表示第 i 行该列为 NULL
    is_null[i] = 0 → 使用 values[i]
  
  
字符串/二进制:
  额外有 length 数组(指定每行字符串长度)
  values 是变长拼接

6. STMT 用于查询

复制代码
参数化查询:

  Prepare("SELECT * FROM meters WHERE location=? AND ts > ?")
  
  Bind:
    ts column: [...]  -- 不适用于查询
    实际:bind 单值参数
  
  Execute → 返回结果集
  
  
查询用 STMT 的好处:
  - 防 SQL 注入
  - 避免重复 Parser
  - 应用层模板化


查询参数绑定较少用,因为查询通常多变;写入参数绑定收益最大。

7. 错误处理

复制代码
常见错误:

  错误码 / 含义:
  - 0x80002659: Parameter 数量不匹配
  - 0x80002650: 列类型不匹配
  - 0x80002653: Bind 数据为空
  - 0x80000404: 子表 USING 时 TAGS 不全
  
  
错误恢复策略:
  - Bind/Execute 失败 → 检查参数
  - 网络错误 → 重新 Prepare 后重试
  - 子表不存在 → 用 USING 自动建表

8. STMT2 vs STMT1

特性 STMT1 STMT2
单表 Bind
多表交织
自动建表 USING ✓(更高效)
性能 极高
推荐 兼容旧版 新项目首选

代码示例

Python STMT2 写入

python 复制代码
import taos
import datetime

conn = taos.connect(...)
stmt = conn.statement2("INSERT INTO ? USING meters TAGS(?,?) VALUES(?,?,?,?)")

# 准备数据
sub_tables = ['d001', 'd002']
tags = [['Beijing', 2], ['Shanghai', 3]]
data = [
    [  # d001
        [int(datetime.datetime.now().timestamp()*1000)+i for i in range(100)],
        [25.0 + i*0.01 for i in range(100)],
        [220]*100,
        [0.5]*100,
    ],
    [  # d002
        [int(datetime.datetime.now().timestamp()*1000)+i for i in range(100)],
        [26.0 + i*0.01 for i in range(100)],
        [221]*100,
        [0.6]*100,
    ],
]
stmt.bind_param(sub_tables, tags, data)
stmt.execute()
print(f"写入 {stmt.affected_rows} 行")

Java STMT 示例

java 复制代码
String sql = "INSERT INTO ? USING meters TAGS(?,?) VALUES(?,?,?,?)";
try (TSWSPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSWSPreparedStatement.class)) {
    pstmt.setTableName("d001");
    pstmt.setTagString(0, "Beijing");
    pstmt.setTagInt(1, 2);
    pstmt.setTimestamp(0, timestamps);
    pstmt.setFloat(1, currents);
    pstmt.setInt(2, voltages);
    pstmt.setFloat(3, phases);
    pstmt.columnDataAddBatch();
    pstmt.columnDataExecuteBatch();
}

性能考量

写入性能阶梯

方式 单连接 RPS 备注
单行 SQL INSERT ~5K 极慢
批量 SQL INSERT (1000 行/批) ~800K 通用
Schemaless ~600K 自动建表
STMT1 单表 ~1M 高效
STMT2 交织(100 子表) ~3M+ 极致

调优要点

建议
单批行数 1000~10000
单批子表数 50~500
并发连接数 CPU 核数
服务端配置 增加 mqStream/numOfRpc

FAQ

Q1: STMT 比 Schemaless 快多少?

通常 3~5 倍。Schemaless 需文本解析+Schema 比对;STMT 二进制+列式+缓存计划。

Q2: STMT 能动态改 SQL 吗?

不能。SQL 在 Prepare 时固定。改 SQL 需要重新 Prepare。

Q3: STMT 写入失败如何重试?

捕获错误后可:

  • 直接 Re-Execute(同样数据)
  • 也可重 Bind 后 Execute
  • 若 Prepare 失效需重新 Prepare

Q4: 多线程能共享同一个 STMT 吗?

不建议。每个线程独立 conn + stmt 最稳定。共享会有锁竞争。

Q5: TAGS 能省略吗?

USING 子句必须提供 TAGS。已存在的子表可省略 USING 直接 INSERT INTO 子表。

参考

系统构架篇

数据模型

存储引擎

查询引擎

数据写入

关于 TDengine

TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。