TDengine 连接算子 — Inner/Outer/ASOF/Window Join 的实现与使用

分类 :4.查询引擎 | 篇章:08 连接算子

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

JOIN 是关系数据库的核心能力。TDengine 在标准 SQL JOIN(Inner/Left/Right/Full)之外,针对时序场景额外提供 ASOF JOIN(按时间近邻)和 Window JOIN(按时间窗口),让"两个设备时间序列对齐"这类需求一行 SQL 即可表达。

核心概念速查表

概念 说明
Hash Join 用哈希表的等值连接
Merge Join 输入已排序时的连接
Inner Join 仅返回两侧匹配的行
Outer Join LEFT/RIGHT/FULL 保留一侧或两侧
ASOF Join 时间近邻连接(找最接近的时间点)
Window Join 时间窗口连接(同窗口内对齐)
Equi Join Condition 等值连接条件(必须含时间等值)

详细解析

1. TDengine JOIN 的时间约束

复制代码
TDengine 的 JOIN 核心要求:
  
  ✓ JOIN 必须包含时间戳等值条件(普通 JOIN)
    或时间相关条件(ASOF / Window JOIN)
  
  示例(合法):
    SELECT * FROM t1 JOIN t2 ON t1.ts = t2.ts;
    SELECT * FROM t1 JOIN t2 ON t1.ts = t2.ts AND t1.id = t2.id;
    
  ✗ 不合法:
    SELECT * FROM t1 JOIN t2 ON t1.id = t2.id;
    -- 缺少时间条件
  
  原因:
  - 时序数据按时间分布
  - 没有时间约束的 JOIN 是笛卡尔积级别的开销
  - 强制时间条件保证可优化为窗口对齐

2. Inner Join 与 Outer Join

复制代码
JOIN 类型对比:

  数据示例:
    t1:                    t2:
    ts=T1, v=10           ts=T1, v=100
    ts=T2, v=20           ts=T3, v=300
    ts=T3, v=30
    
  INNER JOIN (ts=ts):
    T1: (10, 100)
    T3: (30, 300)
    [T2 不匹配,被丢弃]
    
  LEFT JOIN:
    T1: (10, 100)
    T2: (20, NULL)        ← 保留左侧
    T3: (30, 300)
    
  RIGHT JOIN:
    T1: (10, 100)
    T3: (30, 300)
    [t1 中无 T2 也无影响,因为 RIGHT 保留右侧]
    
  FULL OUTER JOIN:
    T1: (10, 100)
    T2: (20, NULL)
    T3: (30, 300)

3. ASOF JOIN(时间近邻)

复制代码
ASOF JOIN 场景:

  设备 A 每秒采集,设备 B 每 5 秒采集
  问题:A 的每个时间点对应的 B 最近一次采集?
  
  数据:
    A: T1, T2, T3, T4, T5, T6, T7, T8
    B: T1, T6
  
  ASOF JOIN A LEFT ASOF JOIN B ON A.ts >= B.ts:
    每个 A.ts 找到 B 中 <=A.ts 的最大者
    
    A.T1 → B.T1
    A.T2 → B.T1
    A.T3 → B.T1
    A.T4 → B.T1
    A.T5 → B.T1
    A.T6 → B.T6
    A.T7 → B.T6
    A.T8 → B.T6


语法:
  SELECT a.ts, a.v, b.v 
  FROM ta a 
  ASOF JOIN tb b 
    ON a.ts >= b.ts 
    AND a.id = b.id  -- Tag 等值约束(可选);

支持的比较操作:
  >, >=, <, <=

4. Window JOIN(时间窗口)

复制代码
Window JOIN 场景:

  问题:每对设备的同一分钟内的关联事件
  
  Window JOIN tb b WINDOW(1m) ON ta.id = tb.id:
    每个 ta 的行,找 tb 中 [ta.ts - 30s, ta.ts + 30s] 内的所有行
    
  示例:
    ta: T1=12:00:10, T2=12:00:50
    tb: B1=12:00:15, B2=12:01:30
    
    ta=T1 → tb 在 [11:59:40, 12:00:40] → B1 匹配
    ta=T2 → tb 在 [12:00:20, 12:01:20] → 无匹配


语法:
  SELECT * FROM ta 
  WINDOW JOIN tb 
    WINDOW(1m)        -- 窗口大小
    ON ta.id = tb.id;

5. Hash Join 实现

复制代码
Hash Join 的两阶段:

  阶段 1:构建(Build)
    选择较小的表(构建侧)
    读取所有行 → 构建哈希表(Key = JOIN 键)
    
  阶段 2:探测(Probe)
    扫描较大的表(探测侧)
    对每行:用 JOIN 键查找哈希表
    匹配则输出
  
  
示例:
  SELECT * FROM big JOIN small ON big.id = small.id AND big.ts = small.ts
  
  Build (small):
    哈希表:
      (id=1, ts=T1) → row_data
      (id=1, ts=T2) → row_data
      (id=2, ts=T1) → row_data
      
  Probe (big):
    扫描 big 每行
    查询哈希表是否有匹配
    
特点:
  ✓ 适合 = 等值连接
  ✓ 大小表组合
  ✗ 构建侧必须放入内存

6. Merge Join 实现

复制代码
Merge Join(输入已排序):

  前提:两侧输入按 JOIN 键有序
  
  算法:
    指针 i 指向 t1 第一行
    指针 j 指向 t2 第一行
    
    while i < len(t1) and j < len(t2):
      if t1[i].key == t2[j].key:
        输出 (t1[i], t2[j])
        i++ 或 j++(处理重复键)
      elif t1[i].key < t2[j].key:
        i++
      else:
        j++


TDengine 中的时间 JOIN 天然适合 Merge Join:
  - 两侧数据都按 ts 有序
  - 不需要构建哈希表
  - 内存占用 O(1)
  - 适合海量数据

7. JOIN 的分布式执行

复制代码
跨 VGroup 的 JOIN 执行:

  SELECT * FROM ta JOIN tb ON ta.ts = tb.ts AND ta.id = tb.id
  
  ta 跨 VGroup 1, 2
  tb 跨 VGroup 3, 4
  
  执行选项:
  
  ① 广播 JOIN(适合小表):
     - 小表(如 tb)拉取到所有 ta 所在节点
     - 每个 ta 节点本地 Hash Join
     
  ② Shuffle JOIN(适合大表):
     - 两侧都按 JOIN 键 Shuffle 到相同节点
     - 各节点 Hash/Merge Join
     - 适合大表 + 大表
     
  ③ 单子表 JOIN(最简单):
     - 如果 ta 和 tb 都是子表
     - 通常单 VGroup 内完成
     - 无需 Shuffle

8. JOIN 性能调优

复制代码
JOIN 性能关键点:

  ① 选择性优先:
     先过滤再 JOIN
     SELECT * FROM ta JOIN tb ON ta.ts=tb.ts 
     WHERE ta.location='BJ' AND tb.location='BJ'
     → 过滤下推到 Scan
     → JOIN 输入数据量减少
     
  ② 时间范围必须明确:
     SELECT * FROM ta JOIN tb ON ta.ts=tb.ts WHERE ta.ts > now-1h
     → 同时限制 ta 和 tb 的时间范围
     
  ③ 数据局部性:
     同 VGroup 的子表 JOIN → 无 Shuffle
     跨 VGroup 的 JOIN → Shuffle 开销

代码示例

基础 JOIN

sql 复制代码
-- 两个超级表的时间对齐
SELECT a.ts, a.current, b.power 
FROM electric_meters a 
JOIN power_meters b 
  ON a.ts = b.ts AND a.location = b.location
WHERE a.ts > now-1h;

-- LEFT JOIN 保留所有 A
SELECT a.ts, a.current, b.power 
FROM electric_meters a 
LEFT JOIN power_meters b 
  ON a.ts = b.ts AND a.location = b.location;

ASOF JOIN

sql 复制代码
-- 高频设备对低频参考值
SELECT a.ts, a.current, b.standard_voltage 
FROM realtime_sensor a 
LEFT ASOF JOIN reference_sensor b 
  ON a.ts >= b.ts 
  AND a.location = b.location
WHERE a.ts > now-1h;

Window JOIN

sql 复制代码
-- 找出每个温度异常前后 1 分钟的湿度记录
SELECT t.ts AS temp_ts, h.ts AS humi_ts, t.temperature, h.humidity 
FROM temperature_log t 
WINDOW JOIN humidity_log h 
  WINDOW(1m) 
  ON t.location = h.location 
WHERE t.temperature > 40 
  AND t.ts > now-1d;

性能考量

JOIN 类型选择

场景 推荐 JOIN
等频率采集对齐 INNER JOIN ON ts
不同采集频率对齐 LEFT ASOF JOIN
事件关联(同窗口内任意点) WINDOW JOIN
维度表关联 INNER JOIN(含 Tag 等值)

性能优化清单

  • WHERE 同时限制两侧的时间范围
  • WHERE 同时过滤两侧的 Tag(让数据局部化)
  • 优先选具体列,避免 SELECT *
  • 小表放右侧(可能影响 Build/Probe 选择)
  • 大基数 JOIN 考虑 QNode

FAQ

Q1: 为什么我的 JOIN 报"missing time condition"?

TDengine 要求 JOIN 必须有时间相关条件。改写:

  • 普通 JOIN:ON ... AND t1.ts = t2.ts
  • 时间近邻:用 ASOF JOIN
  • 时间窗口:用 WINDOW JOIN

Q2: ASOF JOIN 性能如何?

输入按 ts 有序时(时序数据天然如此),用 Merge 风格算法,复杂度 O(N+M)。生产环境处理百万级数据行毫秒~秒级。

Q3: 多表 JOIN(≥3)支持吗?

支持但复杂度高,每多一张表 JOIN 次数线性增加。建议:

  • 拆分为多个简单查询
  • 用应用层组合
  • 或预先 ETL 到宽表

Q4: JOIN 和 UNION 哪个更适合?

  • JOIN:横向合并(增加列)
  • UNION:纵向合并(增加行)
  • 多设备同类数据汇总用 UNION
  • 多种数据类型对齐用 JOIN

参考

系统构架篇

数据模型

存储引擎

查询引擎

相关推荐
白帽小丑3 小时前
# 一次 MySQL DELETE 误操作的数据恢复尝试实录
数据库·mysql
code_pgf4 小时前
AI-Agent记忆机制分析
大数据·人工智能
Quincy_Freak6 小时前
信创内网数据规范实践:银河麒麟下SQLite本地数据安全管理方案
数据库·sqlite·arm·数据库管理·大数据分析·银河麒麟·aarch64
cc5725026537 小时前
挑选大数据专业院校,重点参考哪几项指标
大数据
2601_962683898 小时前
治理遗留系统中的“生肉 SQL”:一次用多模型协作优化慢查询的实战复盘
数据库·人工智能·sql
酱学编程8 小时前
【从零到一实现一个 AI Agent 框架 · 第四篇】04. 任务规划:拆解复杂目标 -
服务器·网络·数据库·人工智能
shushangyun_9 小时前
2026智能采购商城系统选型指南:如何引领企业数字化采购升级
java·大数据·数据库·人工智能·机器学习
华山令狐虫10 小时前
DBAPI AI 写 SQL:支持动态 SQL 与参数占位符,自然语言一键生成
数据库·人工智能·sql·dbapi
IvorySQL11 小时前
PG 技术日报|2026-07-04
数据库·人工智能·postgresql·开源
Hoxy.R11 小时前
KingbaseES读写分离高可用集群扩容、备库重建与故障切换实战
运维·数据库