TDengine 语义分析与 AST 重写 — Catalog 校验、列绑定与表达式规范化

分类 :4.查询引擎 | 篇章:03 语义分析

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

语义分析(Translator)是连接 Parser 与 Planner 的桥梁。它验证 SQL 在语义上是否合法(表是否存在、列类型是否匹配、聚合函数使用是否正确等),并对 AST 做规范化重写,为后续计划生成准备一棵"干净"的逻辑表达。

核心概念速查表

概念 说明
Catalog 客户端缓存的数据库元数据(库/表/列/Tag)
Type Check 类型推导与类型一致性校验
Name Resolution 标识符绑定(列名→具体表的某列)
Constant Folding 常量折叠(编译期计算表达式)
AST Rewrite 重写 AST(展开 *、补默认列等)
Implicit Cast 隐式类型转换

详细解析

1. 语义分析的整体流程

复制代码
Translator 五大步骤:

  AST (Parser 输出)
       │
       ▼
  ① Catalog 查询:
     - 查询 Mnode 获取库/表/Schema/VGroup 信息
     - 缓存到客户端 Catalog Cache(避免重复请求)
       │
       ▼
  ② 名字解析(Name Resolution):
     - 表名 → 实际表对象
     - 列名 → 具体表的列
     - 处理别名(AS)
     - 解决歧义(多表 JOIN 时的同名列)
       │
       ▼
  ③ 类型推导与校验:
     - 函数参数类型校验
     - 二元运算的类型兼容性
     - 必要时插入隐式 CAST
       │
       ▼
  ④ 语义规则校验:
     - 聚合函数使用规则
     - GROUP BY 与 SELECT 列的一致性
     - 时序专属语法的合法性
       │
       ▼
  ⑤ AST 重写:
     - 展开 *
     - 常量折叠
     - 子查询展开
     - 视图替换
       │
       ▼
  净化后的 AST → 交给 Planner

2. Catalog 元数据获取

复制代码
Catalog 缓存机制:

  客户端首次查询某表:
    SELECT * FROM power.meters WHERE ts > now-1h
         │
         ▼
    Catalog Cache 查找 "power.meters"
         │
    ┌────┴─────┐
    │ 未命中    │  命中 ↓
    ▼          │
  请求 Mnode    │
    │          │
    ▼          ▼
  获取:       使用缓存
  - Schema     - Schema 版本号
  - VGroup 分布 - 检查是否需要刷新
  - Tag 元数据
    │
    ▼
  写入 Cache(带 TTL)
  
  缓存失效场景:
  - ALTER TABLE 修改 Schema(版本号变化)
  - DROP TABLE
  - 主动 RESET QUERY CACHE

3. 名字解析示例

复制代码
列名绑定过程:

  SQL: SELECT t.ts, t.current 
       FROM meters t 
       WHERE t.location='BJ'
  
  Translator 处理:
    ① t → 解析为 meters 的别名
    ② t.ts → 绑定到 meters.ts (TIMESTAMP)
    ③ t.current → 绑定到 meters.current (FLOAT)
    ④ t.location → 绑定到 meters.location (BINARY) [Tag]
    
  歧义示例:
    SELECT id FROM t1 JOIN t2 ON t1.id=t2.id
    → 报错:column 'id' is ambiguous
    → 必须写 t1.id 或 t2.id
  
  星号展开:
    SELECT * FROM meters
    → 展开为列出所有列:ts, current, voltage, ...
    → 不包含 Tag(需要写 SELECT *, location)

4. 类型推导与隐式转换

复制代码
类型推导规则:

  ① 二元运算的结果类型:
     INT + INT → INT
     INT + FLOAT → FLOAT
     FLOAT * DOUBLE → DOUBLE
     VARCHAR + VARCHAR → 错误(不支持字符串拼接)
     
  ② 比较运算的类型对齐:
     ts > '2024-01-01' 
     → 字符串自动转 TIMESTAMP
     
     current > 5
     → 整数 5 转 FLOAT(与 current 类型对齐)
     
  ③ 函数参数类型:
     AVG(VARCHAR) → 错误(AVG 只能用于数值类型)
     CONCAT(INT, VARCHAR) → INT 自动转 VARCHAR
显式 CAST 语法 示例
CAST(expr AS TYPE) CAST(ts AS BIGINT)
类型转换函数 TO_CHAR(ts, 'yyyy-mm-dd')

5. 聚合函数的语义校验

复制代码
SELECT 列与聚合函数的关系校验:

  规则:SELECT 列表中的非聚合列必须出现在 GROUP BY 中
  
  ✓ 合法:
    SELECT location, AVG(current) FROM meters GROUP BY location
    
  ✗ 非法:
    SELECT location, current, AVG(current) FROM meters GROUP BY location
    → location 在 GROUP BY 中 ✓
    → current 既不是聚合也不在 GROUP BY ✗
    → 报错:column must appear in GROUP BY
    
  特殊:
    SELECT TBNAME, LAST(*) FROM meters GROUP BY TBNAME
    → TBNAME 在 GROUP BY → 合法
    → LAST(*) 是聚合 → 合法
  
  时序专属:
    SELECT _wstart, _wend, COUNT(*) FROM meters 
    INTERVAL(1h)
    → 窗口函数下,_wstart/_wend 是隐式分组键
    → 不需要写在 GROUP BY 中

6. AST 重写示例

复制代码
常见的重写规则:

  ① 星号展开:
     SELECT * FROM meters
     → SELECT ts, current, voltage, phase FROM meters
     
  ② 常量折叠:
     SELECT * FROM t WHERE ts > now - 60*60*1000
     → SELECT * FROM t WHERE ts > 1700000000000
     
  ③ 谓词重写:
     WHERE NOT (a < 5)
     → WHERE a >= 5
     
  ④ 子查询展开(简单情况):
     SELECT * FROM (SELECT * FROM t WHERE c>0) WHERE c<10
     → SELECT * FROM t WHERE c>0 AND c<10
     
  ⑤ 视图替换:
     CREATE VIEW v AS SELECT * FROM t WHERE c>0;
     SELECT * FROM v WHERE c<10
     → SELECT * FROM t WHERE c>0 AND c<10

7. 时序专属语法的语义检查

复制代码
时序场景的特殊校验:

  ① INTERVAL 窗口校验:
     SELECT COUNT(*) FROM meters INTERVAL(1h) SLIDING(2h)
     → 错误:SLIDING 必须 ≤ INTERVAL
     
  ② FILL 子句校验:
     SELECT AVG(c) FROM t WHERE ts > ... FILL(LINEAR)
     → 错误:FILL 必须与 INTERVAL/EVENT_WINDOW 等窗口一起使用
     
  ③ PARTITION BY 校验:
     SELECT _wstart, COUNT(*) FROM meters 
     PARTITION BY tbname 
     INTERVAL(1h)
     → 合法
     
  ④ STATE_WINDOW 列校验:
     SELECT COUNT(*) FROM meters STATE_WINDOW(status)
     → status 必须是非数值/字符串类型,或离散值
     → 不能用 TIMESTAMP / FLOAT

8. Tag 过滤的特殊处理

复制代码
Tag 过滤的优化路径:

  SQL: SELECT * FROM meters 
       WHERE location='Beijing' AND ts > now-1h

  Translator 识别:
    - location 是 Tag
    - ts 是普通列(数据列)
  
  分离过滤条件:
    Tag 过滤:location='Beijing'
    Data 过滤:ts > now-1h
  
  Tag 过滤的提前处理:
    ① 调用 Catalog/META 的 Tag 索引
    ② 返回符合条件的子表 uid 列表
    ③ 后续 Scan 只针对这些 uid
  
  效果:
    - 100 万子表中只有 1 万属于 'Beijing'
    - 跳过 99 万子表的数据文件读取

代码示例

触发常见语义错误

sql 复制代码
-- 错误 1:表不存在
SELECT * FROM nonexistent_table;
-- error: Table does not exist

-- 错误 2:列名歧义
SELECT id FROM t1, t2;
-- error: ambiguous column name 'id'

-- 错误 3:聚合规则违反
SELECT location, current FROM meters GROUP BY location;
-- error: 'current' must appear in GROUP BY

-- 错误 4:类型不兼容
SELECT current + name FROM meters;
-- error: invalid operation between FLOAT and VARCHAR

利用语义优化提速

sql 复制代码
-- 推荐:常量在右侧 + 时间范围 + Tag 过滤
SELECT * FROM meters 
WHERE location='Beijing' 
  AND ts > now - 1h 
  AND current > 10;
-- ✓ Tag 过滤提前
-- ✓ 时间裁剪 File Set
-- ✓ 数据过滤下推到 Scan

-- 不推荐:函数包裹列字段
SELECT * FROM meters WHERE DATE(ts) = '2024-01-01';
-- ✗ ts 被函数包裹 → 无法使用时间裁剪

Catalog Cache 控制

sql 复制代码
-- 手动刷新 Catalog 缓存
RESET QUERY CACHE;

-- 查看缓存命中情况(系统表)
SELECT * FROM performance_schema.perf_apps;

性能考量

Catalog 请求开销

场景 开销
首次查询新表 1 次 Mnode RPC(~10ms)
命中 Cache 微秒级(本地哈希查找)
Schema 变更后首次查询 重新拉取,~10ms
跨大量库/表的复杂查询 多次 Catalog 请求叠加

语义分析优化建议

场景 建议
高频查询 使用 STMT 避免重复语义分析
程序生成 SQL 表/列名预校验,避免运行时失败
大量 IN 列表 拆分多次查询或改 Tag 查询

FAQ

Q1: Catalog 缓存什么时候自动失效?

服务端 Schema 变更后,下一次查询请求会通过 Mnode 检测版本号不一致 → 自动拉取新 Schema → 更新本地 Cache。客户端无需手动干预。

Q2: SELECT * 会展开 Tag 吗?

不会。SELECT * 只展开普通数据列。如需 Tag,必须显式写出:SELECT *, location, groupid FROM meters,或使用 SELECT TAGS *(部分版本支持)。

Q3: 隐式 CAST 会损失精度吗?

可能。如 BIGINT → FLOAT 在大数值时会丢失精度。FLOAT → INT 会截断小数。推荐显式 CAST 并预判精度影响。

Q4: 子查询会被优化吗?

简单子查询会被展开(合并到外层)。复杂子查询(含聚合、不同窗口、CTE)会保留并生成嵌套计划。语义分析阶段不做优化决策,由 Planner 决定是否展开。

参考

系统构架篇

数据模型

存储引擎

查询引擎

关于 TDengine

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

相关推荐
人工智能培训2 小时前
用知识图谱重构搜索引擎
大数据·人工智能·3d·重构·知识图谱·agent
fengxin_rou2 小时前
Java垃圾回收机制深度解析:从原理到实战
java·jvm·性能优化·gc·垃圾回收
数智化管理手记2 小时前
轻量化落地!四维精益TPM实操体系,根治车间设备故障难题
大数据·数据挖掘
弗锐土豆2 小时前
使用eclipse、java、maven、j60870、oceanbase按照IEC104协议采集、存储电力数据
java·oceanbase·电表·iec104·抄表
小则又沐风a2 小时前
进程最终篇---进程控制(模拟实现xshell)
java·linux·服务器·前端
番石榴AI2 小时前
JiaJiaOCR-2.2.0:面向Java ocr的开源库
java·ocr
码云骑士2 小时前
【3.Java基础】Java运算符详解:从算数运算到逻辑判断,一篇文章全部掌握
java·开发语言
天行健,君子而铎2 小时前
闭环式 AI 降噪成熟可靠型数据安全平台数据流转监测通用解决方案
大数据·人工智能
Web打印2 小时前
HttpPrinter web打印控件 官方文档(https://wiki.httpprinter.com/)快速检索目录
java·javascript·chrome