TDengine 扫描算子 — TableScan、TagScan 与下推优化

分类 :4.查询引擎 | 篇章:06 扫描算子

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

扫描算子是查询树的叶子,负责从存储层读取数据。TDengine 的扫描算子针对时序数据深度优化:时间裁剪、Tag 索引、SMA 预聚合、谓词下推、列裁剪一系列机制让 Scan 阶段就能过滤掉绝大部分无效数据。

核心概念速查表

概念 说明
TableScan 标准表扫描,读取数据列
TagScan 仅扫描 Tag(不读 TSDB 数据)
SystemTableScan 系统表扫描(information_schema 等)
Time Range Filter 时间范围裁剪(File Set 级)
Tag Index Filter Tag 索引过滤(uid 列表)
Block Skip 基于 SMA 的数据块跳过
Column Pruning 列裁剪(只读 SELECT 中的列)

详细解析

1. TableScan 的核心职责

复制代码
TableScan 输入与输出:

  输入:
  - 表名(超级表/子表)
  - 时间范围 [tsMin, tsMax]
  - Tag 过滤条件
  - 数据列过滤条件
  - 需要的列列表
  - VGroup ID(分布式查询时)
  
  输出:
  - 列式 DataBlock(每块默认 4096 行)
  - 包含请求的列 + ts 列
  - 数据按 ts 升序(默认)或降序(ORDER BY ts DESC)


TableScan 内部流程:

  ① 根据时间范围确定相关 File Set
  ② 根据 Tag 过滤确定相关子表 uid 列表
  ③ 对每个子表:
     a. 打开 MemTable 读取器
     b. 打开 STT 文件读取器
     c. 打开 .data 文件读取器
     d. 多路归并合并(详见 04-file-format)
  ④ 应用数据列过滤
  ⑤ 投影出需要的列
  ⑥ 组装 DataBlock 输出

2. 时间裁剪

复制代码
时间裁剪三个级别:

  ① File Set 级:
     SQL: WHERE ts > '2024-06-01'
     → 跳过所有 fid_max < '2024-06-01' 的 File Set
     → 减少打开文件数
     
  ② Block 级:
     File Set 内每个数据块有 [minTs, maxTs]
     → 跳过 maxTs < 查询条件的块
     → 减少解压数据量
     
  ③ Row 级:
     块内逐行比较 ts
     → 跳过不符合的行


时间裁剪的失效场景:

  ✗ WHERE DATE(ts) = '2024-06-01'
     → ts 被函数包裹,无法下推
     
  ✗ WHERE ts + 3600 > now
     → 表达式复杂,可能不下推
  
  ✓ 正确写法:
    WHERE ts BETWEEN '2024-06-01' AND '2024-06-02'
    WHERE ts > now - 1h

3. Tag 过滤与索引

复制代码
Tag 过滤的执行路径:

  SELECT * FROM meters WHERE location='BJ' AND ts > now-1h

  ① Translator 阶段:
     分离 Tag 条件:location='BJ'
     
  ② Catalog/META 查询:
     ┌─ 查 Tag 索引:
     │   Key=(suid_meters, col_location, 'BJ')
     │   → 返回 uid 列表 [101, 205, 308, ...]
     │
     └─ 索引未命中 → 全表扫描 Tag 表过滤
     
  ③ TableScan 仅针对这些 uid 扫描数据
  
  
Tag 索引的覆盖:

  ✓ 等值查询:location='BJ' → 索引精确命中
  ✓ IN 查询:location IN ('BJ','SH') → 多次索引查询
  ✓ 范围查询(数值 Tag):group_id BETWEEN 1 AND 10
  
  ✗ 不能用索引:
     LIKE 'BJ%'(前缀查询有限支持,看版本)
     NOT location='BJ'
     函数包裹的 Tag

4. SMA 预聚合的利用

复制代码
SMA 加速聚合查询:

  SELECT COUNT(*) FROM meters WHERE ts > now-1d
  
  无 SMA:
    扫描所有数据块 → 累加每块的 nRows
    
  有 SMA:
    读取 .sma 文件 → 直接累加块级 COUNT
    无需读取 .data → 大幅加速


  SELECT AVG(current) FROM meters
  
  AVG = SUM/COUNT
  → 读取 .sma 中每块的 SUM 和 COUNT
  → 直接计算结果
  
  
适用 SMA 的函数:
  COUNT, SUM, MIN, MAX, AVG(= SUM/COUNT)

不适用 SMA 的:
  - 含 WHERE 数据列条件(如 WHERE current > 5)
    → 块级 SUM 包含不符合条件的行
  - DISTINCT 类聚合
  - 自定义 UDF

5. 列裁剪

复制代码
列裁剪的效果:

  表有 100 列:ts, c1, c2, ..., c100
  
  查询 1: SELECT * FROM meters WHERE ts > now-1h
  → Scan 读取所有 100 列
  → I/O:100 列字节流
  
  查询 2: SELECT ts, c1 FROM meters WHERE ts > now-1h
  → Scan 只读 ts + c1(2 列)
  → I/O:减少 98%
  → 解压时间也减少 98%


列裁剪触发条件:
  - SELECT 明确列出列名(非 *)
  - 列在 WHERE/GROUP BY/ORDER BY 中也算被引用
  - 列裁剪规则在 Logical Plan 优化阶段标记

6. 谓词下推到 Scan

复制代码
WHERE 条件的执行位置:

  SELECT * FROM meters 
  WHERE ts > now-1h 
    AND current > 10 
    AND voltage < 250

  传统:Filter 算子在 Scan 上方
    Filter
      └── Scan (返回所有行)
    
  谓词下推:条件传给 Scan 算子
    Scan (filter = current>10 AND voltage<250)
    
  下推后:
  - 在解压时即过滤
  - 不构造无用的 DataBlock
  - 减少向上层传输的数据量


下推的条件:
  ✓ 简单列比较:col op constant
  ✓ AND 连接的简单谓词
  ✓ 列在比较的左侧(避免函数包裹)
  
  部分下推(一部分进 Scan,一部分留在 Filter):
  - WHERE simple_cond AND complex_expr
    → simple_cond 下推
    → complex_expr 仍在 Filter

7. TagScan(仅扫 Tag)

复制代码
TagScan 的场景:

  SELECT DISTINCT location FROM meters
  → 只需读 Tag,不读数据列
  → TagScan 直接读 META → 跳过 TSDB
  
  SELECT TBNAME, location FROM meters
  → TagScan 输出子表名 + Tag 值
  
  对比 TableScan:
  - TableScan: 读 TSDB(.data 文件) + META
  - TagScan: 仅读 META → 速度极快(毫秒级)


典型应用:
  SHOW TABLES → TagScan
  SHOW TABLES LIKE 'd1%' → TagScan + 字符串过滤
  SELECT 子表元信息 → TagScan

8. 系统表扫描

复制代码
SystemTableScan:

  访问 information_schema 和 performance_schema 表:
  
  SELECT * FROM information_schema.ins_tables;
  SELECT * FROM information_schema.ins_columns;
  SELECT * FROM performance_schema.perf_queries;
  
  这类查询:
  - 不走 TSDB,直接查 Mnode 内存元信息
  - 数据量小(通常元数据 < 1MB)
  - 适合监控、运维脚本

  注意:
  - ins_tables 在大集群可能返回百万行
  - 建议带 LIMIT 或过滤条件

代码示例

触发各种 Scan 优化

sql 复制代码
-- 优化 1:时间裁剪 + 列裁剪
SELECT ts, current 
FROM meters 
WHERE ts BETWEEN '2024-06-01' AND '2024-06-02';

-- 优化 2:Tag 索引
SELECT * FROM meters 
WHERE location IN ('BJ','SH','GZ') 
  AND ts > now-1h;

-- 优化 3:SMA 加速(无数据过滤条件)
SELECT COUNT(*), AVG(current) 
FROM meters 
WHERE ts > now-1d;

-- 优化 4:TagScan(只查 Tag)
SELECT DISTINCT location FROM meters;

反优化案例

sql 复制代码
-- 反例 1:函数包裹 ts → 失去时间裁剪
SELECT * FROM meters 
WHERE TO_CHAR(ts, 'yyyy-mm') = '2024-06';
-- 改为:WHERE ts >= '2024-06-01' AND ts < '2024-07-01'

-- 反例 2:函数包裹 Tag → 失去索引
SELECT * FROM meters 
WHERE LOWER(location) = 'bj';
-- 改为:保证写入时已规范化

-- 反例 3:SELECT * → 无法列裁剪
SELECT * FROM meters WHERE ts > now-1h;
-- 改为:SELECT ts, current, voltage FROM ...

性能考量

各种扫描的延迟参考

场景 数据规模 延迟
单子表 + 时间范围 1 小时数据 1~10ms
全表 + SMA 聚合 万子表 × 1 天 50~200ms
Tag 索引命中 100/百万子表 5~20ms
TagScan 万子表 10~50ms
全表 + 数据过滤 万子表 × 1 天 500ms~5s
全表无时间条件 数月数据 数分钟(不推荐)

Scan 阶段的优化清单

  • WHERE 包含时间范围
  • Tag 条件等值或 IN
  • SELECT 列明确
  • 避免函数包裹列
  • 聚合查询尽量不带数据列过滤(享受 SMA)

FAQ

Q1: 没有时间条件的 SELECT 危险吗?

非常危险。会触发全 File Set 扫描,可能涉及数百 GB 数据。生产环境应在客户端强制要求时间条件,或在表结构上加合理的 KEEP 限制总数据量。

Q2: SMA 文件占多大空间?

每个数据块对应一个 SMA 记录(约几十字节)。SMA 总大小通常是 .data 文件的 1%~5%。收益(查询加速)远大于空间代价。

Q3: ORDER BY ts DESC 如何执行?

TableScan 支持指定扫描方向。ORDER BY ts DESC 时,Scan 从最新数据反向读取,无需全量排序。但只有"按 ts 排序"才能这样优化,其他列的 ORDER BY 需要显式 Sort 算子。

Q4: PARTITION BY tbname 与逐表查询哪个快?

PARTITION BY tbname 由查询引擎统一调度,比应用层逐表查询发起 N 次请求要快得多。引擎内部会按 VGroup 并行处理。

参考

系统构架篇

数据模型

存储引擎

查询引擎

相关推荐
Georgeviewer6 小时前
商业落地评测|实体门店GEO优化性价比与服务体系深度复盘
大数据·人工智能
YOU OU7 小时前
Redis基础常识与命令
数据库·redis·缓存
腻害兔7 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
GIR1238 小时前
官方出品 | 多通道土壤呼吸测量系统市场现状与十五五规划深度报告:行业分析+趋势预测全收录
大数据·人工智能·机器学习
AimerMilet8 小时前
物联网卡流量池是什么意思?企业用划算吗?
物联网
南讯股份Nascent9 小时前
洽洽全域会员项目启动会圆满召开
大数据·人工智能
大郭鹏宇9 小时前
基于 LangGraph 构建智能分诊系统(一):项目概述与环境搭建
大数据·人工智能·microsoft·langchain
Database_Cool_9 小时前
OLTP 和 OLAP 区别详解:分析型数据库和事务型数据库怎么选(附阿里云 AnalyticDB MySQL 选型指南)
数据库·mysql·阿里云
Xxtaoaooo10 小时前
DolphinDB 物联网数据平台全景:一份从架构到落地的实践地图
物联网·系统架构·时序数据库·数据库架构·dolphindb
爱写代码的阿森10 小时前
鸿蒙三方库 | harmony-utils之KvUtil键值型数据库操作详解
数据库·华为·harmonyos·鸿蒙·huawei