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 写入
importtaosimportdatetime 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)+iforiinrange(100)],[25.0+i*0.01foriinrange(100)],[220]*100,[0.5]*100,],[# d002[int(datetime.datetime.now().timestamp()*1000)+iforiinrange(100)],[26.0+i*0.01foriinrange(100)],[221]*100,[0.6]*100,],]stmt.bind_param(sub_tables,tags,data)stmt.execute()print(f"写入{stmt.affected_rows}行")Java STMT 示例
Stringsql="INSERT INTO ? USING meters TAGS(?,?) VALUES(?,?,?,?)";try(TSWSPreparedStatementpstmt=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 与查询优化》
数据写入
- 01-《TDengine SQL INSERT》
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。