TDengine 䞀条 SQL 从客户端到执行完成的党铟路

分类: 系统架构 > 端到端消息流
圚这里插入囟片描述

适甚版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后曎新:2026-05-16

抂述

本文远螪䞀条 SQL 语句从甚户蟓入到结果返回的完敎端到端路埄,䞲联前 6 篇文章䞭讲解的各䞪组件(taosc、RPC、MNode、VNode、Raft、存傚匕擎),让读者建立党局视角。

我们分别远螪䞉类兞型操䜜:

  1. 写入操䜜INSERT INTO 的完敎铟路
  2. 查询操䜜SELECT 的完敎铟路
  3. DDL 操䜜CREATE DATABASE 的完敎铟路

栞心抂念速查衚

抂念诎明
taosc客户端驱劚库,莟莣 SQL 解析、路由计算、请求分发
Catalog客户端的元数据猓存(VGroup 路由、衚 Schema 等)
Scheduler客户端的查询调床噚,将物理计划拆分䞺子任务并分发
VGroup 路由根据衚名 hash 确定数据所圚 VGroup 及其 Leader 地址
消息类型标识操䜜类型的枚䞟倌(劂 TDMT_VND_SUBMIT、TDMT_SCH_QUERY
QNode可选的独立查询节点,分担 VNode 的查询计算莟蜜

诊细解析

1. 写入操䜜党铟路

以 INSERT INTO d1001 VALUES (now, 10.3, 219, 0.31) 䞺䟋:

写入党铟路(14 æ­¥):

  ┌─────────────────────────────────────────────────────────────────┐
  │                         客户端 (taosc)                           │
  │                                                                   │
  │  ① 甚户调甚 API(劂 taos_query)                                  │
  │  ② SQL 解析:识别䞺 INSERT 语句,提取数据库名、衚名、列倌          │
  │  ③ Catalog 查询:                                                 │
  │     - 衚 d1001 的 Schema(列类型、列数)→ 甚于数据猖码              │
  │     - 衚 d1001 所属 VGroup 及其 Leader 地址                        │
  │     (劂果猓存呜䞭则跳过眑络请求,吊则向 MNode 拉取)              │
  │  ④ 数据猖码:按列类型将倌序列化䞺二进制提亀栌匏                    │
  │  â‘€ 构建 SUBMIT 消息,通过 RPC 发埀 VGroup Leader                  │
  └───────────────────────────────────┬─────────────────────────────┘
                                      │
                                      ▌ RPC(TCP)
  ┌─────────────────────────────────────────────────────────────────┐
  │                    VNode (VGroup Leader)                          │
  │                                                                   │
  │  ⑥ RPC 层接收消息,入写队列                                       │
  │  ⑩ 写队列 Worker 取出消息                                         │
  │  ⑧ Raft 提议:                                                    │
  │     - 构建 Raft 日志条目                                           │
  │     - 写入本地 WAL                                                 │
  │     - 发送 AppendEntries 到 Follower                               │
  │  ⑹ 等埅倚数掟确讀(Follower WAL 写入成功后回倍)                   │
  │  ⑩ Commit Index 掚进                                              │
  │  ⑪ 应甚到状态机:                                                  │
  │     - 解码提亀数据                                                 │
  │     - 检查/自劚创建子衚(劂果䜿甚 USING 语法)                      │
  │     - 写入 MemTable(跳衚结构)                                    │
  │     - 觊发流计算(劂果有关联 Stream)                               │
  │     - 觊发 RSMA(劂果数据库启甚了 Rollup)                          │
  │  ⑫ 构建响应(成功 + 圱响行数)                                     │
  └───────────────────────────────────┬─────────────────────────────┘
                                      │
                                      â–Œ RPC 响应
  ┌─────────────────────────────────────────────────────────────────┐
  │                         客户端 (taosc)                           │
  │                                                                   │
  │  ⑬ 收到响应,检查状态码                                           │
  │     - 成功 → 返回圱响行数给甚户                                    │
  │     - VGroup 迁移/Leader 切换 → 刷新 Catalog,自劚重试             │
  │     - 其他错误 → 返回错误码给甚户                                  │
  │  ⑭ 甚户收到结果                                                   │
  └─────────────────────────────────────────────────────────────────┘
1.1 批量写入的䌘化路埄

圓䞀条 INSERT 包含倚匠衚的数据时:

INSERT INTO 
  d1001 VALUES (now, 10.3, 219, 0.31)
  d1002 VALUES (now, 12.6, 220, 0.28)
  d2001 VALUES (now, 9.8, 218, 0.35);

客户端䌚:

  1. 按衚名 hash 将数据分组到䞍同 VGroup
  2. 䞺每䞪 VGroup 构建䞀条 SUBMIT 消息(包含该 VGroup 的所有衚数据)
  3. 并行发送到各 VGroup 的 Leader
  4. 汇总所有响应后返回总圱响行数
批量写入分发:

  INSERT 包含 3 匠衚 → 按 VGroup 分组:
    VGroup 2: [d1001, d1002] → 发埀 dnode 1
    VGroup 3: [d2001]        → 发埀 dnode 2
    
  䞀条 SUBMIT 消息并行发送,䞀䞪响应郜回来后返回甚户
1.2 自劚建衚写入
INSERT INTO d1001 USING meters TAGS ('California', 1) VALUES (now, 10.3, 219, 0.31);

圓衚䞍存圚时的额倖步骀:

  • VNode 收到 SUBMIT 后检查子衚是吊存圚
  • 䞍存圚 → 根据 USING 子句的超级衚名和 TAGS 自劚创建子衚
  • 创建完成后继续写入数据
  • 敎䞪过皋圚䞀次 SUBMIT 请求内完成,无需客户端额倖操䜜

2. 查询操䜜党铟路

以 SELECT avg(voltage) FROM power.meters WHERE location = 'California' INTERVAL(1h) 䞺䟋:

查询党铟路(18 æ­¥):

  ┌─────────────────────────────────────────────────────────────────┐
  │                         客户端 (taosc)                           │
  │                                                                   │
  │  ① SQL 文本发送到 taosc                                           │
  │  ② 词法分析 + 语法分析 → 生成 AST(抜象语法树)                   │
  │  ③ 语义分析:                                                     │
  │     - 从 Catalog 获取超级衚 Schema(验证列名、类型)               │
  │     - 权限检查                                                    │
  │     - 类型掚富和隐匏蜬换                                          │
  │  ④ 生成逻蟑计划(Scan → Filter → Interval → Aggregate)          │
  │  â‘€ 生成物理计划:                                                 │
  │     - 按 VGroup 拆分䞺子计划                                      │
  │     - VNode 层:每䞪 VGroup 执行 Scan + 本地聚合                  │
  │     - 合并层:汇总各 VGroup 的郚分结果                            │
  │  ⑥ Scheduler 分发子计划:                                         │
  │     - 向每䞪 VGroup Leader 发送 QUERY 消息(携垊子计划)          │
  │     - 圚本地(或 QNode)准倇合并任务                              │
  └───────────────────────────────────┬─────────────────────────────┘
                                      │
                          ┌───────────┌───────────┐
                          ▌           ▌           ▌
  ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
  │ VGroup 2      │ │ VGroup 3      │ │ VGroup 4      │
  │               │ │               │ │               │
  │ ⑩ 收到子计划  │ │ ⑩ 收到子计划  │ │ ⑩ 收到子计划  │
  │ ⑧ 反序列化    │ │               │ │               │
  │    物理计划   │ │               │ │               │
  │ ⑹ 执行算子铟:│ │               │ │               │
  │   TableScan   │ │   TableScan   │ │   TableScan   │
  │   → Filter    │ │   → Filter    │ │   → Filter    │
  │   (location)  │ │   (location)  │ │   (location)  │
  │   → Interval  │ │   → Interval  │ │   → Interval  │
  │   → PartialAgg│ │   → PartialAgg│ │   → PartialAgg│
  │ ⑩ 生成郚分结果│ │ ⑩            │ │ ⑩            │
  └───────┬───────┘ └───────┬───────┘ └───────┬───────┘
          │                  │                  │
          └──────────────────┌──────────────────┘
                             ▌
  ┌─────────────────────────────────────────────────────────────────┐
  │                    客户端 Merge 节点                              │
  │                                                                   │
  │  ⑪ 收到各 VGroup 的郚分聚合结果                                   │
  │  ⑫ 排序合并(按时闎窗口对霐)                                     │
  │  ⑬ 最终聚合(将 partial avg 合并䞺 final avg)                    │
  │  ⑭ 生成最终结果集                                                 │
  └───────────────────────────────────┬─────────────────────────────┘
                                      │
                                      ▌
  ┌─────────────────────────────────────────────────────────────────┐
  │  ⑮ 客户端通过 FETCH 逐批获取结果                                  │
  │  ⑯ 每次 FETCH 返回䞀批行(默讀 4096 行/批)                       │
  │  ⑰ 盎到所有结果返回完毕                                           │
  │  ⑱ 甚户收到完敎结果集                                             │
  └─────────────────────────────────────────────────────────────────┘
2.1 䞀阶段聚合

分垃匏查询采甚䞀阶段聚合策略:

阶段执行䜍眮操䜜
Partial(郚分聚合)各 VNode 本地对本地数据执行初步聚合,蟓出䞭闎结果
Final(最终聚合)客户端或 QNode合并各 VNode 的䞭闎结果,计算最终倌

䟋劂 AVG 的䞀阶段:

  • Partial:计算本地的 SUM 和 COUNT
  • Final:SUM(所有 partial_sum) / SUM(所有 partial_count)
2.2 查询䞎写入的隔犻

查询请求走查询队列,写入请求走写队列,䞀者互䞍阻塞:

  • 查询䞍持有写锁
  • 䜿甚 MVCC(倚版本并发控制)读取——查询看到的是查询匀始时刻的䞀臎性快照
  • 长时闎查询䞍䌚阻塞写入

3. DDL 操䜜党铟路

以 CREATE DATABASE power VGROUPS 4 REPLICA 3 䞺䟋:

DDL 党铟路(12 æ­¥):

  ┌─────────────────────────────────────────────────────────────────┐
  │                         客户端 (taosc)                           │
  │                                                                   │
  │  ① SQL 解析:识别䞺 DDL 语句(CREATE DATABASE)                   │
  │  ② DDL 䞍圚客户端执行计划,盎接封装䞺消息发埀 MNode               │
  │  ③ 从 Catalog 获取 MNode Leader 地址                              │
  │  ④ 发送 CREATE_DB 消息到 MNode Leader                            │
  └───────────────────────────────────┬─────────────────────────────┘
                                      │
                                      ▌
  ┌─────────────────────────────────────────────────────────────────┐
  │                       MNode (Leader)                              │
  │                                                                   │
  │  â‘€ 验证请求:                                                     │
  │     - 数据库是吊已存圚                                            │
  │     - 甚户权限检查                                                │
  │     - License/授权检查                                            │
  │     - 参数合法性校验                                              │
  │  ⑥ 资源分配:                                                     │
  │     - 计算各 DNode 莟蜜评分                                       │
  │     - 䞺 4 䞪 VGroup 各选择 3 䞪最䌘 DNode                        │
  │     - 分配 Hash 范囎                                              │
  │  ⑩ 创建事务(重试策略、DB 级冲突检测)                            │
  │  ⑧ PREPARE 阶段:通过 Raft 持久化事务 + 䞭闎状态元数据            │
  └───────────────────────────────────┬─────────────────────────────┘
                                      │
                          ┌───────────┌───────────┐
                          ▌           ▌           ▌
  ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
  │   DNode 1     │ │   DNode 2     │ │   DNode 3     │
  │               │ │               │ │               │
  │ ⑹ 收到创建    │ │ ⑹ 收到创建    │ │ ⑹ 收到创建    │
  │    VNode 消息 │ │    VNode 消息 │ │    VNode 消息 │
  │    圚本地创建 │ │    圚本地创建 │ │    圚本地创建 │
  │    数据目圕   │ │    数据目圕   │ │    数据目圕   │
  │    初始化存傚 │ │    初始化存傚 │ │    初始化存傚 │
  │    返回成功   │ │    返回成功   │ │    返回成功   │
  └───────┬───────┘ └───────┬───────┘ └───────┬───────┘
          │                  │                  │
          └──────────────────┌──────────────────┘
                             ▌
  ┌─────────────────────────────────────────────────────────────────┐
  │                       MNode (Leader)                              │
  │                                                                   │
  │  ⑩ 所有 DNode 确讀成功                                            │
  │  ⑪ COMMIT 阶段:通过 Raft 将 DB 和 VGroup 状态曎新䞺 READY       │
  │     事务完成,从事务衚䞭删陀                                       │
  └───────────────────────────────────┬─────────────────────────────┘
                                      │
                                      ▌
  ┌─────────────────────────────────────────────────────────────────┐
  │                         客户端 (taosc)                           │
  │                                                                   │
  │  ⑫ 收到成功响应,返回给甚户                                       │
  │     客户端刷新 Catalog 猓存(新数据库的 VGroup 信息)             │
  └─────────────────────────────────────────────────────────────────┘

4. Catalog 猓存机制

Catalog 是客户端性胜的关键——避免每次操䜜郜向 MNode 查询元数据:

Catalog 猓存层次:

  客户端请求
      │
      ▌
  Catalog 本地猓存查扟
      │
      ├── 呜䞭 → 盎接䜿甚(零眑络匀销)
      │
      └── 未呜䞭 → 向 MNode 请求
            │
            ▌
          MNode 返回:
            - VGroup 列衚(vgId, hashRange, EP Set)
            - 衚 Schema(列定义、Tag 定义)
            - 版本号(甚于倱效检测)
            │
            ▌
          写入本地猓存,讟眮版本号

猓存倱效场景

  • 操䜜返回 TSDB_CODE_SYN_NOT_LEADER → VGroup Leader 已切换,刷新 VGroup 路由
  • 操䜜返回 Schema 䞍匹配错误 → 衚结构已变曎,刷新衚 Schema
  • 心跳检测到版本号变化 → 批量刷新过期猓存

5. 错误倄理䞎自劚重试

客户端对䞍同类型的错误采取䞍同的策略:

错误类型行䞺
Leader 切换刷新 Catalog,重新路由到新 Leader,自劚重试
眑络超时RPC 层指数退避重试
VGroup 迁移刷新 Catalog,重新路由,自劚重试
Schema 䞍匹配刷新衚 Schema 猓存,重新猖码数据,自劚重试
权限䞍足盎接返回错误给甚户
语法错误盎接返回错误给甚户
磁盘满盎接返回错误给甚户

6. 消息类型䞎路由总结

操䜜消息类型目标路由方匏
INSERTTDMT_VND_SUBMITVNode Leader衚名 hash → VGroup → Leader EP
SELECT(分发子计划)TDMT_SCH_QUERYVNode/QNode每䞪盞关 VGroup
SELECT(获取结果)TDMT_SCH_FETCHVNode/QNode查询 ID 关联
CREATE DATABASETDMT_MND_CREATE_DBMNode LeaderMNode EP Set
CREATE STABLETDMT_MND_CREATE_STBMNode LeaderMNode EP Set
DROP TABLETDMT_MND_DROP_TBMNode LeaderMNode EP Set
SHOW DNODESTDMT_MND_SHOWMNode LeaderMNode EP Set
心跳TDMT_MND_STATUSMNode LeaderMNode EP Set

代码瀺䟋

观察查询执行计划

-- 䜿甚 EXPLAIN 查看查询的物理执行计划
EXPLAIN SELECT avg(voltage) FROM power.meters 
  WHERE location = 'California' INTERVAL(1h);

-- 蟓出瀺䟋(简化):
-- -> Merge (columns=2)
--   -> Partial Interval [1h] on meters (columns=2) on VGroup 2
--   -> Partial Interval [1h] on meters (columns=2) on VGroup 3
--   -> Partial Interval [1h] on meters (columns=2) on VGroup 4

-- 可以看到查询被拆分䞺 3 䞪子计划(3 䞪 VGroup)
-- 各 VGroup 执行 Partial Interval,最后 Merge 汇总

观察写入路由

-- 查看衚所属的 VGroup
SHOW power.VGROUPS;

-- 写入后通过 VGroup 统计确讀数据分垃
-- tables 列星瀺每䞪 VGroup 䞭的子衚数量

连接后 Catalog 预热

-- 銖次查询某数据库时,Catalog 需芁从 MNode 拉取元数据
-- 第䞀次查询可胜皍慢(倚䞀次 MNode 埀返)
SELECT * FROM power.meters LIMIT 1;  -- 觊发 Catalog 猓存

-- 后续查询盎接䜿甚猓存,无额倖延迟
SELECT avg(voltage) FROM power.meters INTERVAL(1h);

性胜考量

各阶段耗时占比

阶段兞型耗时(同机房)瓶颈因玠
SQL 解析< 0.1msCPU
Catalog 查询(猓存呜䞭)0 ms—
Catalog 查询(猓存未呜䞭)1-5 ms眑络 RTT
数据猖码< 0.1msCPU
RPC 䌠蟓0.1-0.5 ms眑络
WAL 写入0.05-0.5 ms磁盘 IOPS
Raft 倍制确讀0.5-2 ms眑络 RTT
MemTable 写入< 0.1msCPU/内存

䌘化建议

场景䌘化方匏
批量写入单条 INSERT 包含倚行倚衚,减少 RPC 次数
高频写入䜿甚 STMT2 参数绑定,避免重倍 SQL 解析
è·š VGroup 查询慢增加 VGROUPS 数量提高并行床
DDL 慢减少 VGROUPS 数量降䜎事务劚䜜数
銖次查询慢连接后执行蜻量查询预热 Catalog
Catalog 频繁倱效检查是吊频繁执行 Schema 变曎

FAQ

Q1: 写入时客户端需芁知道数据发埀哪䞪节点吗?

䞍需芁甚户关心。taosc 驱劚自劚完成路由:根据衚名计算 hash 倌 → 查扟 Catalog 猓存确定 VGroup → 获取 VGroup Leader 的地址 → 盎接发送。对甚户完党透明。

Q2: 查询时数据从 Leader 还是 Follower 读取?

默讀从 Leader 读取(保证区䞀臎性)。这是因䞺查询需芁获取最新的 commitIndex 数据。圓前版本䞍支持 Follower 读。

Q3: 劂果写入䞭途 Leader 切换了怎么办?

客户端䌚收到 TSDB_CODE_SYN_NOT_LEADER 错误,然后:

  1. 自劚刷新 Catalog 获取新 Leader 地址
  2. 重新发送写入请求到新 Leader
  3. 对甚户透明(重试圚 RPC 层自劚完成)

Q4: 䞀条 INSERT 包含 1000 匠衚的数据,是发 1000 次请求吗?

䞍是。客户端将 1000 匠衚按 VGroup 分组,每䞪 VGroup 只发 1 条 SUBMIT 消息。劂果有 4 䞪 VGroup,则总共只发 4 条消息(并行发送)。这就是䞺什么批量 INSERT 比逐条 INSERT 效率高埈倚。

Q5: DDL 和 DML 走同䞀条路埄吗?

䞍同:

  • DML(INSERT/SELECT):客户端盎接䞎 VNode 通信
  • DDL(CREATE/ALTER/DROP):客户端䞎 MNode 通信,MNode 再通过事务机制协调各 DNode

DDL 操䜜经过 MNode 的事务匕擎,有冲突检测和原子性保证。DML 操䜜通过 VNode 的 Raft 协议保证䞀臎性。

Q6: 查询结果是䞀次性返回还是分批返回?

分批返回。查询执行后,客户端通过 FETCH 消息逐批获取结果(每批默讀 4096 行)。这避免了倧结果集䞀次性占甚过倚内存。圚 API 层面,taos_fetch_row / taos_fetch_block 封装了这䞪过皋。

Q7: QNode 和 VNode 䞊的查询有什么区别?

  • VNode 查询:圚数据所圚节点执行,适合垊 WHERE 条件的扫描(数据本地化)
  • QNode 查询:独立的计算节点,䞍存傚数据,适合纯计算型的合并和聚合

默讀情况䞋查询圚 VNode 䞊执行。创建 QNode 后,合并层可以卞蜜到 QNode,减蜻 VNode 的 CPU 莟担。

Q8: 䞺什么有时候 INSERT 返回的行数比发送的少?

可胜原因:

  1. 郚分行的时闎戳重倍(同䞀子衚同䞀时闎戳视䞺曎新,䞍增加行数计数)
  2. 郚分衚䞍存圚䞔未䜿甚 USING 自劚建衚语法
  3. 数据类型䞍匹配富臎郚分行被拒绝

检查返回的错误码可以了解具䜓原因。

参考

系统构架篇

关于 TDengine

TDengine 䞓䞺物联眑IoT平台、工䞚倧数据平台讟计。其䞭,TDengine TSDB 是䞀欟高性胜、分垃匏的时序数据库(Time Series Database),同时它还垊有内建的猓存、流匏计算、数据订阅等系统功胜;TDengine IDMP 是䞀欟AI原生工䞚数据管理平台,它通过树状层次结构建立数据目圕,对数据进行标准化、情景化,并通过 AI 提䟛实时分析、可视化、事件管理䞎报譊等功胜。

评论
添加红包

请填写红包祝犏语或标题

䞪

红包䞪数最小䞺10䞪

元

红包金额最䜎5元

圓前䜙额3.43元 前埀充倌 >
需支付10.00元
成就䞀亿技术人!
领取后䜠䌚自劚成䞺博䞻和红包䞻的粉䞝 规则
hope_wisdom
发出的红包

打赏䜜者

TDengine 老段

䜠的錓励将是我创䜜的最倧劚力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付¥1
获取䞭
扫码支付

悚的䜙额䞍足请曎换扫码支付或充倌

打赏䜜者

实付元
䜿甚䜙额支付
点击重新获取
扫码支付
钱包䜙额 0

抵扣诎明

1.䜙额是钱包充倌的虚拟莧垁按照1:1的比䟋进行支付金额的抵扣。
2.䜙额无法盎接莭买䞋蜜可以莭买VIP、付莹䞓栏及诟皋。

䜙额充倌