分类 :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 子表。
参考
系统构架篇
- 01-《TDengine 整体架构全景》
- 02-《集群拓扑深度解析》
- 03-《MNode 内部机制深度解析》
- 04-《RPC 通信层深度解析》
- 05-《VNode 生命周期》
- 06-《RAFT 共识协议》
- 07-《端到端的消息流》
数据模型
- 01-《数据库创建与参数详解》
- 02-《超级表/子表/普通表》
- 03-《支持数据类型深度解析》
- 04-《TDengine Tag 设计哲学与 Schema 变更机制》
- 05-《TDengine 虚拟表实现原理》
存储引擎
- 01-《TDengine 存储引擎概览》
- 02-《TDengine MemTable 深度解析》
- 03-《TDengine WAL 预写日志机制》
- 04-《TDengine 数据文件格式》
- 05-《TDengine Commit 与 Flush 机制 》
- 06-《TDengine Compaction 合并策略 》
- 07-《TDengine 数据保留与 TTL》
- 08-《TDengine 压缩编码机制》
- 09-《TDengine Cache 与 Last 查询加速》
- 10-《TDengine 逻辑计划生成》
查询引擎
- 01-《TDengine 查询引擎概览》
- 02-《TDengine SQL 解析与词法分析》
- 03-《TDengine 语义分析与 AST 重写》
- 04-《TDengine 逻辑计划生成》
- 05-《TDengine 物理计划生成》
- 06-《TDengine 扫描算子》
- 07-《TDengine 聚合算子》
- 08-《TDengine 聚合算子》
- 09-《TDengine 连接算子》
- 10-《TDengine 排序、填充与投影》
- 11-《TDengine 分布式查询执行》
- 12-《TDengine EXPLAIN 与查询优化》
数据写入
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。