Doris Join 概述:从概念到执行流
各位同学,欢迎来到《Doris多表关联查询性能调优》的第一章。我是你们的老朋友,一个跟分布式数据库打了十几年交道的工程师。今天咱们先不急着调优,先把 Join 这件事本身聊透。
你想想看,数据分析里最常干的事是什么?就是把几张表拼在一起。订单表关联用户表,行为日志关联商品信息……说白了,Join 就是数据世界的「拼图游戏」。但拼不好,性能就崩了。
Join 的基本概念
Join 的核心逻辑,就是根据两个表之间的关联条件,把符合条件的行组合成一行输出。我习惯把参与 Join 的表分成两类:
- 左表(Left Table):驱动表,通常是数据量较小的那个
- 右表(Right Table):被驱动表,通常是数据量较大的那个
举个例子,订单表(orders)关联用户表(users),条件就是 orders.user_id = users.id。Doris 会拿着左表的每一行,去右表里找匹配的行。嗯,这里要注意——匹配不上怎么办?这就引出了不同的 Join 类型。
核心要点:Join 的本质是「行组合」操作。理解了这个,后面看执行计划才不会懵。
Doris 支持的 Join 类型
Doris 支持五种标准 Join 类型。我在项目中见过不少同学把 Left Join 和 Full Join 搞混,咱们一次性说清楚。
| Join 类型 | 语义说明 | 返回行数 | 典型场景 |
|---|---|---|---|
| Inner Join | 只返回两表匹配的行 | ≤ 左表行数 | 订单与支付记录匹配 |
| Left Join | 返回左表所有行,右表无匹配则补 NULL | ≥ 左表行数 | 用户列表及其订单(无订单也保留用户) |
| Right Join | 返回右表所有行,左表无匹配则补 NULL | ≥ 右表行数 | 商品列表及其销量(无销量也保留商品) |
| Full Join | 返回两表所有行,无匹配侧补 NULL | 两表行数之和 | 数据对账、差异分析 |
| Cross Join | 笛卡尔积,两表行数相乘 | 左表行数 × 右表行数 | 生成测试数据、枚举组合 |
避坑指南:我曾经接手过一个慢查询,开发同学写了个 Left Join 但条件里加了右表的过滤,结果 Doris 自动转成了 Inner Join,数据对不上。记住——右表过滤条件写在 WHERE 里会改变 Join 语义,要写在 ON 子句中才能保持 Left Join 的效果。
Join 的执行流程概览
Doris 执行一个 Join 查询,大致走这么几步。我画了张图,你一看就明白。
流程看起来不复杂,对吧?但实际跑起来,每一步都可能成为瓶颈。我挑几个关键点说说:
- SQL 解析阶段:Doris 会检查表是否存在、字段是否合法、Join 条件是否有效。这个阶段一般很快,但如果 SQL 写得特别长(比如几百行子查询嵌套),解析也会慢。
- 执行策略选择:这是最核心的一步。Doris 会根据左右表的数据量、数据分布、Join 类型,决定用 Broadcast Join 还是 Shuffle Join。我个人的经验是——小表广播、大表 Shuffle,这个原则能解决 80% 的性能问题。
- 数据分发:Shuffle 阶段会把数据按照 Join key 重新分区。如果 Join key 的分布不均匀(比如某个用户 ID 占了 90% 的数据),就会产生数据倾斜。嗯,这个问题我们后面会专门用一章来讲。
我的小技巧:在 Doris 里,你可以用 EXPLAIN 命令查看执行计划。比如 EXPLAIN SELECT ... FROM A JOIN B ON ...,Doris 会告诉你它选择了哪种 Join 策略。我每次写复杂查询都会先看一眼执行计划,确认策略对不对再跑。
一个简单的例子
光说不练假把式。咱们看个实际例子:
-- 创建订单表
CREATE TABLE orders (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10,2),
order_date DATE
) DISTRIBUTED BY HASH(user_id) BUCKETS 10;
-- 创建用户表
CREATE TABLE users (
user_id BIGINT,
user_name VARCHAR(50),
city VARCHAR(20)
) DISTRIBUTED BY HASH(user_id) BUCKETS 5;
-- 查询每个用户的订单总额
SELECT u.user_name, SUM(o.amount) AS total_amount
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_name;
这个查询里,users 是左表,orders 是右表。Doris 会先判断哪个表小——如果 users 只有几千行,而 orders 有几千万行,它大概率会选择 Broadcast Join,把小表广播到所有 BE 节点上,避免大表 Shuffle。
但如果你把条件反过来,或者 Join key 的分布有问题,性能可能就天差地别了。这些坑,咱们后面的章节一个一个填。