tencent cloud

TDSQL Boundless

多表关联查询

PDF
聚焦模式
字号
最后更新时间: 2026-03-26 14:55:43
本文档介绍如何在 TDSQL Boundless 数据库中使用 JOIN 语句进行多表连接查询,包括各种连接类型、连接算法的使用。

开始之前

在进行单表查询之前,请确保:
1. 已创建 TDSQL Boundless 实例
2. 已导入测试数据:本文档使用 TPC-H 基准测试数据集作为示例,详情请参考 TPC-H 基准测试数据模型参考
3. 已建立数据库连接:通过 MySQL 客户端或应用程序连接到 TDSQL Boundless。

JOIN 类型

内连接(INNER JOIN)

内连接只返回两张表中同时满足连接条件的行。如果某一侧没有匹配的行,则该行不会出现在结果中。
查询每个订单对应的客户名称:
SELECT
o.o_orderkey,
o.o_orderdate,
o.o_totalprice,
c.c_name,
c.c_mktsegment
FROM orders o
INNER JOIN customer c ON o.o_custkey = c.c_custkey;
在这个查询中,只有在 customer 表中存在对应客户的订单才会被返回。
多表内连接 — 查询订单明细及其所属订单和客户信息:
SELECT
c.c_name,
o.o_orderkey,
o.o_orderdate,
l.l_partkey,
l.l_quantity,
l.l_extendedprice
FROM customer c
INNER JOIN orders o ON c.c_custkey = o.o_custkey
INNER JOIN lineitem l ON o.o_orderkey = l.l_orderkey
WHERE o.o_orderdate BETWEEN '1995-01-01' AND '1995-03-31'
ORDER BY l.l_extendedprice DESC
LIMIT 20;
注意:
由于使用的是 INNER JOIN,没有下过订单的客户不会出现在结果中。如果需要包含没有订单的客户,应使用 LEFT JOIN

左外连接(LEFT JOIN)

左外连接返回左表的所有行。对于右表中没有匹配的行,结果中右表的列填充为 NULL
查询所有客户及其订单数量(包括没有下过订单的客户):
SELECT
c.c_custkey,
c.c_name,
c.c_mktsegment,
COUNT(o.o_orderkey) AS order_count
FROM customer c
LEFT JOIN orders o ON c.c_custkey = o.o_custkey
GROUP BY c.c_custkey, c.c_name, c.c_mktsegment
ORDER BY order_count ASC
LIMIT 20;
INNER JOIN 不同,即使某个客户在 orders 表中没有任何记录,该客户仍然会出现在结果中,其 order_count 为0。
使用 LEFT JOIN 查找不匹配的行 — 查询没有下过订单的客户:
SELECT c.c_custkey, c.c_name, c.c_phone
FROM customer c
LEFT JOIN orders o ON c.c_custkey = o.o_custkey
WHERE o.o_orderkey IS NULL;
这种写法等价于 NOT EXISTS 子查询,但在某些场景下执行效率更高:
-- 等价写法
SELECT c_custkey, c_name, c_phone
FROM customer c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.o_custkey = c.c_custkey
);

右外连接(RIGHT JOIN)

右外连接返回右表的所有行,对于左表中没有匹配的行,左表的列填充为 NULL。其逻辑与 LEFT JOIN 对称。
查询所有国家及其客户数量:
SELECT
n.n_name AS nation_name,
COUNT(c.c_custkey) AS customer_count
FROM customer c
RIGHT JOIN nation n ON c.c_nationkey = n.n_nationkey
GROUP BY n.n_name
ORDER BY customer_count DESC;
实践教程:在实际开发中,建议统一使用 LEFT JOIN 代替 RIGHT JOIN,只需调换表的顺序即可。这样可以保持 SQL 风格一致,提高可读性。
上述查询等价于:
SELECT
n.n_name AS nation_name,
COUNT(c.c_custkey) AS customer_count
FROM nation n
LEFT JOIN customer c ON c.c_nationkey = n.n_nationkey
GROUP BY n.n_name
ORDER BY customer_count DESC;

交叉连接(CROSS JOIN)

交叉连接返回两张表的笛卡尔积,即左表的每一行与右表的每一行组合。如果左表有 m 行、右表有 n 行,结果集将有 m * n 行。
查询所有区域和市场细分的组合:
SELECT
r.r_name AS region_name,
seg.segment_name
FROM region r
CROSS JOIN (
SELECT DISTINCT c_mktsegment AS segment_name
FROM customer
) seg
ORDER BY r.r_name, seg.segment_name;
注意:
交叉连接会产生非常大的结果集,在生产环境中应谨慎使用,通常仅用于生成维度组合等特定场景。

左半连接(LEFT SEMI JOIN)

左半连接用于判断左表的行在右表中是否存在匹配,只返回左表的列,且每行最多返回一次。TDSQL 语法中并不直接支持 LEFT SEMI JOIN 语法,通常通过 EXISTSIN 子查询来表达。
查询有过订单的客户:
SELECT c.c_custkey, c.c_name, c.c_acctbal
FROM customer c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.o_custkey = c.c_custkey
);
等价的 IN 写法:
SELECT c_custkey, c_name, c_acctbal
FROM customer
WHERE c_custkey IN (
SELECT o_custkey FROM orders
);
INNER JOIN 不同,左半连接不会因右表存在多条匹配而产生重复行。例如一个客户有10个订单,INNER JOIN 会返回10行,而左半连接只返回1行。

隐式连接

不使用 JOIN 关键字,而是在 FROM 子句中列出多张表,并在 WHERE 子句中指定连接条件。隐式连接的行为等同于 INNER JOIN
SELECT
c.c_name,
o.o_orderkey,
o.o_totalprice
FROM customer c, orders o
WHERE c.c_custkey = o.o_custkey
AND o.o_orderdate >= '1995-01-01'
ORDER BY o.o_totalprice DESC
LIMIT 10;
实践教程:建议使用显式的 JOIN ... ON 语法代替隐式连接。显式语法将连接条件与过滤条件分离,SQL 的意图更加清晰,也更易于维护。

JOIN 相关算法

TDSQL Boundless 优化器会基于代价评估自动选择最优的连接算法。目前支持两种核心算法:Nested Loop JoinHash Join

Nested Loop Join

Nested Loop Join(嵌套循环连接)是最基础的连接算法。其原理是遍历外表(驱动表)的每一行,然后在内表中查找匹配的行。
TDSQL Boundless 默认采用 Batched Key Access(BKA) 方式执行 Nested Loop Join。BKA 的核心优化思路是:将外表的多行键值批量收集后,一次性发送给内表进行查找,而不是逐行发送。这种批处理方式有两个显著优势:
1. 减少 RPC 频次:将多次单行查找合并为一次批量请求,大幅降低网络开销
2. 提升存储引擎读取效率:内表收到一批键值后,批量索引查找和回表,提升存储引擎读取效率
适用场景
内表的连接列上有索引
外表数据量相对较小,或经过 WHERE 过滤后行数较少
索引选择性好(即通过索引能快速定位少量行)
示例
-- 典型的 Nested Loop Join 场景
-- orders 作为外表,customer 通过主键 c_custkey 作为内表
SELECT
o.o_orderkey,
o.o_totalprice,
c.c_name
FROM orders o
INNER JOIN customer c ON o.o_custkey = c.c_custkey
WHERE o.o_orderdate = '1995-03-15';
在这个查询中,orders 表经过 o_orderdate 过滤后行数较少,作为驱动表;customer 表的 c_custkey 是主键,作为被驱动表。BKA 会将过滤后的 o_custkey 值批量发送,一次性在 customer 表中查找匹配行。

Hash Join

Hash Join(哈希连接)的原理是:首先将较小的表(构建表)加载到内存中构建哈希表,然后遍历较大的表(探测表),对每一行通过哈希函数快速查找匹配行。
适用场景
连接列上没有合适索引
等值连接(= 条件)
示例
-- 典型的 Hash Join 场景
-- 两张大表的等值连接,且连接列无索引
SELECT
l.l_orderkey,
l.l_extendedprice,
l.l_discount,
p.p_name,
p.p_type
FROM lineitem l
INNER JOIN part p ON l.l_partkey = p.p_partkey
WHERE p.p_type LIKE '%BRASS%';

算法选择

TDSQL Boundless 优化器基于代价模型自动选择连接算法,主要考虑以下因素:
因素
Nested Loop Join (BKA)
Hash Join
索引依赖
需要内表连接列上有索引
不依赖索引
数据量
适合外表较小、内表较大的场景
适合两表都较大的场景
内存消耗
较低,按批次处理
较高,需要在内存中构建哈希表
连接类型
等值和非等值连接
仅支持等值连接
通过 EXPLAIN 可以查看优化器选择的连接算法:
EXPLAIN SELECT
o.o_orderkey, c.c_name
FROM orders o
INNER JOIN customer c ON o.o_custkey = c.c_custkey
WHERE o.o_orderdate = '1995-03-15';

JOIN 顺序

在多表连接查询中,表的连接顺序对查询性能有重要影响。TDSQL Boundless 优化器会自动评估不同的连接顺序,选择代价最低的执行方案。

自动优化

优化器在生成执行计划时,会综合考虑表的大小、索引情况、过滤条件的选择性等因素,自动调整连接顺序:
-- 优化器会自动决定 customer、orders、lineitem 的连接顺序
SELECT
c.c_name,
o.o_orderdate,
l.l_extendedprice
FROM customer c
INNER JOIN orders o ON c.c_custkey = o.o_custkey
INNER JOIN lineitem l ON o.o_orderkey = l.l_orderkey
WHERE c.c_mktsegment = 'BUILDING'
AND o.o_orderdate < '1995-03-15'
AND l.l_shipdate > '1995-03-15';

使用 Hint 指定连接顺序

当优化器选择的连接顺序不理想时,可以通过 Hint 手动干预连接顺序。
JOIN_ORDER:指定所有表的完整连接顺序:
SELECT /*+ JOIN_ORDER(o, c, l) */
c.c_name,
o.o_orderdate,
l.l_extendedprice
FROM customer c
INNER JOIN orders o ON c.c_custkey = o.o_custkey
INNER JOIN lineitem l ON o.o_orderkey = l.l_orderkey
WHERE c.c_mktsegment = 'BUILDING'
AND o.o_orderdate < '1995-03-15'
AND l.l_shipdate > '1995-03-15';
JOIN_ORDER(o, c, l) 强制优化器严格按 orders > customer > lineitem 的顺序执行连接,忽略 SQL 中的表书写位置。
JOIN_PREFIX:仅指定连接顺序的前几张表,其余表由优化器自动决定:
SELECT /*+ JOIN_PREFIX(o, c) */
c.c_name,
o.o_orderdate,
l.l_extendedprice
FROM customer c
INNER JOIN orders o ON c.c_custkey = o.o_custkey
INNER JOIN lineitem l ON o.o_orderkey = l.l_orderkey
WHERE c.c_mktsegment = 'BUILDING'
AND o.o_orderdate < '1995-03-15'
AND l.l_shipdate > '1995-03-15';
JOIN_PREFIX(o, c) 强制前两张表的连接顺序为 orders > customer,而 lineitem 的连接位置由优化器自行选择。当只需要控制部分关键表的顺序、其余表交由优化器优化时,JOIN_PREFIX 比 JOIN_ORDER 更灵活。

帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈