暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

为什么磐维这类 MPP 数据库大表 join 反而更快?

原创 千钧 2025-11-19
241

目录

为什么磐维这类 MPP 数据库大表 join 反而更快?

——反直觉背后的分布式执行真相


第1章 所有人第一次接触 MPP 时都会遇到这道“思想反差”

第一次接触 MPP 的人,都会经历一次“价值观崩塌”。

在单机数据库(Oracle、MySQL、PostgreSQL)里,大家从第一天就被教育:“大表 JOIN 很危险。”

在传统系统中,只要听到“几亿行的大表 JOIN 大表”,开发和 DBA 的第一反应几乎都是本能的:

  • “能不能改成预处理?”
  • “能不能拆成多次查询?”
  • “能不能用缓存?”
  • “要不……先把表缩一缩?”

因为经验告诉我们:大表 JOIN 就像堵车,一旦堵上,整个系统都不好受。

但到了 MPP 系统,一切突然反过来了——这是感受到的第一个冲击。

你把业务迁到 Greenplum、Vertica、OushuDB 或磐维数据库OLAP型这样的 MPP 架构之后,运行的第一条大表 JOIN 常常会发生这样的戏剧性变化:

  • **在单机里:**两张 5 亿行表的 JOIN,能跑到怀疑人生。

  • **在 MPP 里:**同样级别的大表 JOIN,几十秒甚至十几秒就跑完了。

而更匪夷所思的事情发生了:

  • 两个每天千万级的数据事实表 JOIN → 飞快
  • 大表 JOIN 大表 → 稳定、速度还惊人
  • 大表 JOIN 小表 → 偶尔还慢得莫名其妙
  • 小表 JOIN 小表 → 也可能比预期慢很多

这时大多数人的反馈都是:“怎么跟我想的不一样?”、 “不科学啊?”、 “哪里有问题?”、 “是不是优化器在胡来?”,尤其是第一次看到 Explain 的时候,那种违和感更强:

  • 明明大表 JOIN,执行计划却干干净净,全是本地算子
  • 明明小表 JOIN,反而 Motion(数据搬移)满天飞
  • 明明数据库资源很多,但慢的部分总是“网络传输”
  • 明明逻辑一样,性能表现却差得离谱

**为什么这种现象让人困惑?因为我们脑子里的 JOIN 认知来自“单机时代”。在单机数据库里,JOIN 的性能几乎由一件事决定:“表有多大”。**越大越慢,这在单机世界里几乎是自然规律。但 MPP 的世界不是这样运行的。它的运行逻辑背后有两个隐藏前提:

前提 1:表越大,越容易“被并行切得相对均匀”

前提 2:JOIN 的成本不是看表多大,而是“需不需要跨节点搬数据”

这两个前提颠覆了我们对 JOIN 的原有理解,也直接导致:在 MPP 中,大表 JOIN 反而通常“更不容易出错”,更快跑完。

而小表往往因为:

  • 分布不一致
  • JOIN 字段基数低
  • 统计信息不完善
  • 非等值或表达式 JOIN
  • Motion 频繁
  • 倾斜严重

反而成为性能黑洞。所以这里的核心只有一个:重置大家的旧世界观。

我们希望大家意识到:

  • 单机经验 ≠ MPP 经验。
  • 单机直觉 ≠ MPP 真相。
  • 大表 ≠ 慢,小表 ≠ 快。

JOIN 的本质,在 MPP 中完全变成了另一套逻辑。在大家的旧经验里:

  • 大表 JOIN 是“问题源头”
  • 它越大越慢,越让人害怕

但在 MPP 的世界里:

  • 大表 JOIN 是“被平均切开的任务”
  • 算子在多节点间分散执行
  • 网络不动,JOIN 就能飞

所以这章我们只是希望大家先建立一个心理准备:**你以为的 JOIN,不是 MPP 执行器眼里的 JOIN。


第2章 单机 JOIN 的世界:所有 JOIN 都要“挤过一扇门”

在解释“大表 JOIN 为什么在 MPP 里反而更快”之前,我们必须先回到单机数据库的世界,看看它是怎么执行 JOIN 的。只有看清楚“单机为什么慢”,才能真正理解“MPP 为什么快”。

这一章的目的就是把单机 JOIN 的瓶颈讲透。

2.1 单机数据库只有“一扇门”:所有数据必须挤进一个实例

无论是 Oracle、MySQL、PostgreSQL、SQL Server,本质上都有个共同点:所有 SQL 最终都要在一个数据库实例里跑完。

它们当然有多线程、有并发、有缓冲池, 但 JOIN 的核心执行依然要挤过同一个“执行通道”:

  • 同一块 CPU(或者同一组 CPU)
  • 同一块内存(buffer pool)
  • 同一套磁盘路径
  • 同一份中间结果存储空间

你可以把单机数据库理解成:**一台高速收费站,无论你有多少车,都要从这一个口通过。**表越大,车越多,这条道就越堵。


2.2 单机 JOIN 绝不是“扫一遍表”那么简单

很多人以为 JOIN 的成本是:“扫描表 + 匹配行”,其实远不止如此。

单机 JOIN 的主要成本包括:

  • 1) 扫描两张表(或索引)

    而且扫描通常受 I/O 限制。

  • 2) 构建哈希表(Hash Join)或建立嵌套循环(Nested Loop)

    哈希表越大,内存压力越大;溢出后要写临时文件。

  • 3) 搜索匹配行

    大表 → 哈希表大 → CPU 开销大。

  • 4) 合并中间结果

    输出结果可能比输入表还大,进一步施压内存和磁盘。

  • 5) 排序、聚合等额外步骤(如果包含 ORDER/GROUP BY)

以上所有东西都要在同一台机器上完成。 这才是单机 JOIN 越大越慢的根本原因。


2.3 单机 JOIN 的性能瓶颈,本质上是“三个固定”

单机无论 CPU 再强,都无法改变三个事实:

  • 固定的 I/O 带宽

    磁盘吞吐固定。大表必须“从头扫到尾”,无法跳过。

  • 固定的内存容量

    哈希表太大 → spill → 临时文件,临时文件太大 → 随机 I/O → 性能暴死

  • 固定的 CPU 资源

    CPU 超不过物理上限,JOIN 算子再高效也只能实现“有限加速”。

所以单机时代会有一句经验之谈:越大的表,越拖慢 JOIN;越多个 JOIN,越让执行计划恶化。

这不是开发写得差,而是单机执行模型天然如此


2.4 单机数据库的 JOIN 成本模型,是线性的,甚至是超线性的

简单表示如下:

单机数据库的 JOIN 成本模型.png

表越大:

  • 扫描越久
  • 哈希表越大
  • spill 越严重
  • 中间结果越大
  • 随机 I/O 越多

最终 JOIN 的代价可能是超线性增长的。

例如:

表大小 JOIN 预期时间(理想) 实际时间(可能)
1GB 1x 1x
10GB 10x 20–30x
100GB 100x 300–1000x

这就是为什么单机的 DBA 和开发对大表非常敏感。


2.5 单机优化器最怕三件事:大表、大表、大表

单机里所有性能事故的“罪魁祸首”几乎都跟大表有关:

  • 大表 JOIN 大表
  • 大表 JOIN 小表,但选择 Nested Loop
  • 大表上没有合适的索引
  • 大表上的统计信息不准
  • 大表需要排序

最终导致:

只要业务里听到“大表”两个字,大家下意识先紧张。

而这个“根深蒂固的直觉”会在迁移到 MPP 后带来巨大的误解 ——因为你会继续用单机的“恐惧”在看 MPP 查询。

但 MPP 的 JOIN 完全不是这么工作的。


第3章 MPP JOIN 的世界:不再是一扇门,而是 N 扇门 + N 个执行器

上面我们讲清楚了: 单机 JOIN 的慢,本质是 —— 所有数据挤同一扇门

  • 一个 CPU(或少数几个)
  • 一块内存
  • 一组磁盘带宽
  • 一个执行器

这决定了单机 JOIN “越大越慢”几乎是物理定律。但在 MPP(Massively Parallel Processing)架构里,整套逻辑完全翻转:

MPP 不是“把大表交给一台机器算”,

而是“把大表切碎,让几十台机器一起算”。

如果说单机是“一条高速公路的收费站”,那 MPP 就像:几十个收费站一起放车。 而你的 JOIN,就是几十条车道在同时通行。

下面我们完整展开。


3.1 MPP 的核心思想:分布数据、分布计算、分布执行

所有 MPP(Greenplum、Vertica、磐维等)都遵循同一套运行哲学:

(1)数据不是放在一个地方,而是“水平切片”放在多个节点

你的一张 100 亿行大表,实际上长这样:

100亿行大表.png

这是 MPP 的基础: 你以为是一个大表,其实是很多小表拼成的。

(2)每个节点都有自己的执行器(QE)“独立干活”

执行计划在 MPP 里不是一份,而是 N 份:

执行计划在MPP里多份.png

这意味着:

  • 一个 JOIN,本质上是 N 个 JOIN 并行执行
  • 每个节点只处理“自己那一份”
  • 所需的内存也只是 1/N
  • 构建的哈希表也是 1/N
  • 扫描的数据量也是 1/N

大表被“切碎”,每个节点都只在处理“小表”。

这是整个“反直觉”的根源。

(3)JOIN 的执行是完全分布式的

单机 JOIN 是:

一台机器计算所有事情

MPP JOIN 是:

N 台机器分别做自己那份 JOIN

最后再合并结果

就像跑步接力:

  • 每个节点跑自己那段
  • 不是让一个人把全程跑完

所以 JOIN 的速度取决于:

最慢的那个节点,而不是表的绝对大小。

这就导致:

  • 大表被切得很平均 → 跑得很快
  • 小表分布不均 or 需要 shuffle → 可能跑得慢

3.2 MPP 的执行分工:QD 派活、QE 干活

MPP 比单机多一个角色:

  • QD(Query Dispatcher):大脑
  • QE(Query Executor):多节点的工人

整体流程如下:

MPP 的执行分工流程.png

你写的是一条 SQL, 但数据库实际执行的是:

  • 几十份不同的子查询
  • 每个节点执行属于自己的那一份
  • 整体共同完成一条 JOIN

最终 QD 汇总结果返回给你。

3.3 决定 MPP JOIN 快慢的不是表大小,而是“要不要搬数据”(Motion)

这是理解 MPP JOIN 的绝对关键:在 MPP 中,JOIN 的最大成本 = 网络传输(Motion)

NOT:

  • CPU
  • 内存
  • 扫描
  • 哈希
  • 排序

它们都不是第一开销。因为这三者都被并行切开了:

  • CPU 切开 → 每个节点 CPU 足够
  • 内存切开 → 哈希表变小
  • IO 切开 → 扫描更快

真正的瓶颈是:数据是否需要从一个节点广播到另一个节点?

一旦 JOIN 字段不一致、分布键不同,就会触发这两种恶名昭彰的 Motion:

1)Redistribute(重分布)——把数据洗牌一遍

某些 MPP 甚至称其为“洗发水算法”:本地乱、中间乱、对端乱。成本非常高。

2)Broadcast(广播)——把小表全复制一遍

小表每增加 1GB,广播到 16 节点就是 16GB 传输。节点越多,开销越吓人。

结论:

在 MPP 里 JOIN 的“真正敌人”从来不是表大不大,而是:是不是本地 JOIN,不要 Motion!

这就是为什么:

  • 大表 JOIN 大表 → 分布一致 → 本地 JOIN → 快
  • 小表 JOIN 大表 → 分布不一致 → Motion → 慢

3.4 大表在 MPP 看起来“大”?实际上已经被切成“小块”

我们用数字直观感受一下:

假设:

  • 表 A = 10 亿行
  • 表 B = 50 亿行
  • 集群节点数 = 20

单机眼里:

A = 巨型表
B = 超巨型表
JOIN = 天崩地裂

MPP 眼里:

节点 A 子表行数 B 子表行数
Node1 0.5 亿 2.5 亿
Node2 0.5 亿 2.5 亿
Node3 0.5 亿 2.5 亿
Node20 0.5 亿 2.5 亿

每个节点只处理:

  • A 的 1/20
  • B 的 1/20
  • 哈希表大小 1/20
  • 扫描量 1/20

JOIN 的本质变成:“1000万行 JOIN 2500万行” × 20 并行。难度瞬间从“巨无霸大 JOIN”→ “中等 JOIN × 多次并行”。


第4章 为什么大表 JOIN 反而更快?——四个关键原因拆开讲

现在,我们已经知道:

  • 单机 JOIN 的瓶颈是 资源只有一份
  • MPP JOIN 的底层逻辑是 分布式并行
  • 真正决定 JOIN 性能的不是“表大不大”,而是 是否需要跨节点搬数据(Motion)

接下来,我们把整篇文章最关键的问题彻底讲明白:为什么在 MPP 里“大表 JOIN 大表”反而很快?

核心原因有四个,每一个单独看都很合理,放在一起就形成了那种让人“恍然大悟”的反直觉。

4.1 原因 1:大表更容易“分布得均匀”,并行度能完全发挥**

倾斜,是 MPP 的头号敌人。但越大的表,它的分布往往越自然、越均匀。

原因非常简单:

🎯 大表 JOIN 常用的字段(order_id、user_id 等)基数都很高

高基数字段 → hash 后更均匀 → 每个节点分到的数据量接近。

我们用数字举例:

假设:

  • lineitem = 30 亿行
  • orders = 10 亿行
  • 分布键是 order_id(基数巨大)

切到 20 个节点,每个节点大概是:

  • lineitem:1.5 亿行
  • orders:5000 万行

差距不大,负载均衡。

JOIN 在每个节点都像这样跑:

原因1join节点.png

没有哪个节点“特别慢”。

这意味着:

并行度可以 100% 发挥。

这就是为什么你会看到“大表 JOIN 大表”的查询跑得特别稳定、特别快。

4.2 原因 2:大表常常“分布键一致”(DK 一致原则),天然命中本地 JOIN

这是大部分 MPP 初学者忽略的核心。

在数据仓库/大数据场景里,两个大事实表几乎一定会按照相同的业务主键分布:

  • fact_order
  • fact_order_item
  • fact_payment
  • fact_clickstream

这些表共同的分布键通常是:

  • order_id
  • user_id
  • session_id
  • device_id

所以当它们 JOIN 时,天然满足:

JOIN 字段 与 分布键一致(DK 一致)
→ 直接走本地 JOIN
→ 无需要 Redistribute / Broadcast
→ Motion = 0

这是所有 MPP 的最优执行路径。

我们用图表示一下这一幕:

原因2执行路径.png

没有 Motion,没有网络瓶颈,每个节点各扫各的,各算各的。

JOIN 的执行成本变成:max(各节点 JOIN 时间) (而不是所有数据汇聚到一个节点)

这就是你看到“大表 JOIN 大表反而稳定”的原因。反例小表我后面会在例子里对比。

4.3 原因 3:大表扫描在 MPP 中是“顺序 IO + 向量化 + 多节点同时扫”

很多 DBA 看大表会本能紧张:“扫描会很慢吧?”但这是单机时代的直觉。

在现代 MPP(例如磐维数据库OLAP型等)中,大表扫描是整个系统最“擅长”的事情,甚至称得上是:

MPP 最欣赏你给它一个大表,让它尽情顺序扫。

为什么?

1) 大表的文件组织通常是 AO、列存或 MOR(merge-on-read)结构

顺序扫描效率惊人。

2) 扫描行为被切成 20 份、40 份同时进行

单机只能一口气吃;
MPP 是“二十口一起吃”。

3) 向量化执行器对大批量数据最友好

大表比小表更容易触发向量化路径,反而发挥执行器的优势。

4) I/O 在各节点同时拉取

整个集群 I/O 合力 > 单机 N 倍。

所以你会看到一种特别反直觉的体验:**在 MPP 上,扫描越大的表,总体性能越“线性稳定”。**小表反而可能因为“凑不齐批处理”导致执行器效率不高。

换句话说:大表在 MPP 眼里不是“巨兽”,而是“一堆更好处理的小块”。

4.4 原因 4:大表 JOIN 不会触发“广播小表”的昂贵代价

这一条非常非常关键。

很多人误以为:

“小表 JOIN 大表肯定快。”

但在 MPP 中,小表 JOIN 触发 Broadcast 的概率非常高。

Broadcast 的代价是什么?

小表大小 × 节点数量

假设:

  • 小表是 1GB
  • 集群是 16 节点
  • Broadcast = 1GB × 16 = 16GB 传输量

如果维表稍微再大一点,比如 10GB:

10GB × 16 = 160GB

160GB 的网络传输,性能再好的 MPP 都扛不住。

反观大表 JOIN 大表:

  • 分布键一致
  • 没有 Broadcast
  • 也不需要 Redistribute
  • 纯本地 JOIN

不需要动网络,自然更快、更稳。

所以我们第一次得出一个颠覆常识的结论:在 MPP 中,大表 JOIN 大表(只要分布对齐),远比“小表 JOIN 大表”要轻松得多。

4.5 原因 5:大表统计信息更充分,优化器更容易“走对路”

这是容易被忽略但非常真实的一点。

MPP 优化器需要依赖统计信息来判断:

  • 是否广播
  • 是否重分布
  • JOIN 顺序
  • JOIN 算法(Hash / Merge / NestedLoop)
  • Motion 的最优策略

大表由于数据多,统计信息收集:

  • 误差更小
  • 分布更可预测
  • 选择率更稳定

优化器更容易生成正确计划。

但小表呢?

  • cardinality 偏差大
  • 分布可能很偏
  • 选择率经常“猜不准”
  • 可能误判为“小表可广播”
  • 结果走了最糟糕的路径

所以:

大表 JOIN 更容易走到“理想计划”;

小表 JOIN 更容易走到“错误计划”。


第5章 用一个具体场景,让你从头到尾看懂:为什么大表 JOIN 反而快?

为了把前面四个原因“串起来”,我们用一个在几乎所有企业里都真实存在的业务场景:订单事实表 JOIN 订单明细事实表。

这不是虚构案例,而是每个零售、电商、物流行业都必然有的模型。

我们假设场景如下:

1)两张大表:每天产生数据千万级

  • orders(订单表):例如每天 1000 万行
  • lineitem(订单明细表):例如每天 3000 万行

2)两表都按 order_id 分布(DK一致原则)

事实表天然适合按业务主键分布。

3)查询需求:找出最近一天所有订单及其明细

非常常见的场景。

SQL 非常朴素:

select * from lineitem l join orders o on l.order_id = o.order_id where l.dt = '2024-01-01';

5.1 先看单机怎么执行(为什么慢)

假设把同样数据放在单机(哪怕 64 核机器):执行流程如下:

5.1单机怎么执行.png

单机成本:

  • 扫描:40M 行
  • 哈希表:10M 键
  • 探测:30M 次
  • 中间结果写临时空间
  • CPU + 内存 + I/O 全挤在一台机器上

只要数据再涨一点(如 100M+), 单机 JOIN 就很容易开始“喘不上气”。

5.2 再看 MPP:第一步就是把大表切碎

假设集群有 20 个节点,两张表的分布如下:

节点 orders 子表 lineitem 子表
N1 50 万行 150 万行
N2 50 万行 150 万行
N20 50 万行 150 万行

整套操作不再是“40M JOIN”,而是:

每个节点执行“200 万 JOIN 50 万” × 20 并行

这已经把难度从“巨型运算”降成了“中等运算 × 多次并行”。

5.3 JOIN 字段和分布键一致 → 完全本地 JOIN

这一步是关键,也是本例中大表 JOIN 快的原因。

因为两张表都按 order_id 分布,所以每个节点上:

  • lineitem 子表里的 order_id 范围
  • orders 子表里的 order_id 范围

完全对应。

因此 JOIN 完全不需要 Motion:不需要广播,不需要重分布,不需要跨节点传输。

执行图如下:

5.3JOIN字段和分布.png

这是 MPP 最理想执行模式:Local Hash Join,全程不动网络。

5.4 更关键的:每个节点的 JOIN 都是“轻量级”的

在每个节点:

  • 扫描量 ≈ 200 万
  • 构建 hash ≈ 50 万条
  • 探测 hash ≈ 150 万条

对现代 MPP 的向量化执行器来说,这几乎是小菜一碟。

扫描时间短、内存压力小、溢出概率低、CPU 算子效率高。

换句话说:这不是大表 JOIN,而是“20 个小表 JOIN”同时执行。

最终的速度取决于:**20 个节点中最慢的那一个。**但因为大表分布均匀,这个“最慢”节点也不会太慢。

5.5 再看为什么“小表 JOIN 大表”反而容易变慢

我们对比一下这个 SQL:

select * from lineitem l join d_user u on l.user_id = u.user_id;

d_user 小表(例如 2 万行),但问题在于:

  • 可能是随机分布
  • 可能按 user_type 分布(基数低)
  • JOIN 字段不是大表的分布键
  • 统计信息可能不完整

优化器可能选择:

  • Broadcast u
  • Redistribute l
  • 或者两边都 Redistribute(最糟糕)

你会看到这样的执行路径:

5.5执行路径.png

这样一来:

  • lineitem (30M 行)被洗牌
  • d_user (2 万行)被广播
  • Motion × 节点数,网络开销暴涨
  • 分布不对齐 → 可能出现倾斜

最终结论:小表 JOIN 大表,并不一定比大表 JOIN 大表快,小表还可能成为性能的真正瓶颈。

其中“最坑”的是:d_user 很小,但它本身的分布设计非常随意,反而拖垮了整个查询。


5.6 最终结论:看看整个场景的全链路逻辑

我们把两种 JOIN 的全链路总结在一张图里:

✔ 大表 JOIN 大表(DK 一致)

大表 JOIN 大表.png

❌ 小表 JOIN 大表(DK 不一致)

小表 JOIN 大表.png

这就是为什么你在 MPP 上会遇到一个强烈反直觉:大表 JOIN 大表(反而快),小表 JOIN 大表(反而慢)

当你理解了这个例子,你就看懂整个 MPP JOIN 体系的核心规律。


第6章 在磐维数据库OLAP型中,这种差异更加突出

前面几章说的,其实都是所有 MPP 的共性:只要是“切片存储 + 多节点并行 + 分布式执行”的架构,大表 JOIN 反而更快这件事,都会不同程度存在。

那为什么我会专门拉出一个小节,单讲一下磐维数据库OLAP型?不是为了夸一句“磐维更快”,而是想说一件更实际的事:在磐维这样的新一代 MPP 上, “大表 JOIN 反而更快”这件事, 不仅客观存在,而且“看得更清楚、感受更强烈”。

原因主要有三点。

1. 磐维的 JOIN 本来就偏向“本地化 + 向量化”

从架构设计上看,磐维数据库OLAP型属于:

  • MPP 架构(多计算节点并行)
  • 存算分离(计算节点可以弹性扩缩)
  • 配合列式/向量化执行(对大批量扫描和大表 JOIN 更友好)

这直接带来一个效果:

  • 只要分布键选得合理(DK 一致),大表 JOIN 几乎天然走“本地 JOIN + 向量化扫描”的路径;
  • 而一旦触发 Motion(Redistribute / Broadcast),那段网络+调度开销会在 Explain 里非常扎眼。

也就是说:

  • 大表 JOIN,分布对齐 → 很容易跑出“教科书级别”的并行效果;
  • 小表 JOIN,如果分布乱写 → 很容易在执行计划里暴露出“Motion 满天飞”的情况。

磐维没有帮你“遮丑”,反而把 MPP 的执行逻辑摊开给你看

2. Explain 把 Motion、“大表本地 JOIN”这些行为直接摊在表面上

在很多传统数据库里,Explain 输出只告诉你:

  • 走了什么索引
  • 用了什么 JOIN 算法
  • 大概扫描行数是多少

但在磐维这类 MPP 系统里,你能看到的东西明显更多,例如(抽象描述一下):

  • 是否存在 Redistribute Motion / Broadcast Motion
  • Motion 前后的行数估计
  • 各个切片(slice)的执行顺序
  • 每一层算子对应的是QD 还是 QE

你会直观地看到两类典型执行计划:

1) 大表 JOIN 大表、分布对齐:

  • 多个 Segment 上各自 Scan + Hash Join
  • 中间没有或只有极少 Motion
  • 上面只是一层 Gather,把结果汇总回前端

2) 小表 JOIN 大表、分布乱写:

  • 小表被 Broadcast 到所有节点
  • 大表被 Redistribute 一次甚至两次
  • Explain 中 Motion 行数巨大
  • 运动带来的延迟在实际执行时间里非常明显

用一句更口语的话来说:在磐维里,大表 JOIN 快不快、慢在哪里,你基本一眼就能从 Explain 里看出来,不用猜。

这会强化一种直观感受: 原来大表 JOIN 根本不可怕,真正可怕的是“到处 Motion 的 JOIN”。

3. 实际使用中,大家更容易“走到正确的路径上”

还有一个比较微妙但很现实的点:

  • 磐维的典型推荐建模方式,是把订单、明细、支付、日志这类事实表统一按业务主键分布
  • 再加上列存/向量化执行器对大表扫描非常友好,
  • 所以在不刻意调优的情况下,大表 JOIN 本来就更容易落在“正确的执行路径”上

反过来,小表如果随便设计:

  • 分布键乱选
  • 没有统一规范
  • 统计信息也不太在意

那在 Explain 里就会经常看到: 看起来“很轻”的一个维表 JOIN, Motion 的代价甚至比大事实表还高

这就会把本文前面讲的那件事放大得特别明显:**真正拖累 JOIN 的,常常不是大表,而是乱七八糟的小表。**磐维只是把这个事实,用更透明的方式呈现给你。

所以,这里想传递的并不是一句“磐维很厉害”,而是:在磐维这种 MPP 架构里,大表 JOIN 反而快这件事情, 一方面是真实存在,另一方面是“可观测、可解释、可复现的”。

只要你愿意打开 Explain,看一眼 Motion 和各节点的执行片段,就会发现:

  • 之前对“大表 JOIN”的恐惧,大多来自单机时代;
  • 在 MPP + 磐维这样的架构下,大表更多时候是“好学生”,小表才是“捣蛋鬼”。

第7章 在 MPP 里,大表 JOIN 本质上是在“多节点上处理小表 JOIN”

这篇文章想表达的中心思想就是:

  • 在 MPP 里,你眼中的“大表 JOIN”
  • 在系统内部,其实是“很多个小表 JOIN 同时在跑”。

更准确一点说:

  • 你看到的是:

    • 一条 SQL,两个巨大的事实表 JOIN 在一起
  • MPP 看到的是:

    • 20 台机器、40 个执行器,各自拿着自己那一小块数据,在做本地 JOIN

    • 每个 JOIN 的规模都不算大,只是大家一起干活,看起来像“大表 JOIN 跑得飞快”。

这也是本文标题的真正含义:“为什么 MPP 大表 JOIN 反而更快?” 因为它根本不是在做一件“大”的事, 而是在做很多件“小但并行”的事。

换一个更形象的比喻:

  • 在单机里,大表 JOIN 像什么?

    像让一个人, 一口气把几十层的楼梯跑完。 楼层越多,越容易在半路趴下。

  • 在 MPP 里,大表 JOIN 像什么?

    像是把这几十层楼梯,切成很多段, 分给二十个人,每人只跑其中一小段, 然后把所有人的结果拼在一起。只要分配得均匀,每个人跑的那段都不算累, 整体看起来就像“这群人跑楼梯跑得特别轻松”。

  • 而小表 JOIN 大表是什么?

    如果你把楼梯的分段切得乱七八糟,有的人只跑两级台阶,有的人要跑二十层,最终整队的速度,还是被那位跑二十层的人拖住。

在 MPP 中,这个“楼梯怎么切”、“每段分给谁”、 就是我们前面反复提到的:

  • 分布键设计
  • DK 一致原则
  • Motion 开销
  • 倾斜与否

在单机时代, **我们害怕的是“大表”。**在 MPP 时代, 我们真正该害怕的,是“乱分布的小表”和“到处乱飞的 Motion”。

理解了这一点,再看“大表 JOIN 反而更快”,就不再是一个反直觉的怪现象,而是一个可以被设计、被利用、甚至被借力发挥的系统特性

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论