Mysql JOIN 的物理执行流程

一、关联字段在两个表中都没有索引

当两个参与 join 的表在关联字段上都没有索引时,MySQL 无法使用高效的索引树搜索,而是被迫采用 Block Nested-Loop Join (BNL) 算法。

为了清晰讲解物理流程,我们设定如下 SQL 示例 :

  • 表 t1t1t1(驱动表) :100 行数据,字段 bbb 无索引 。
  • 表 t2t2t2(被驱动表) :1000 行数据,字段 bbb 无索引 。
  • SQL 语句select * from t1 straight_join t2 on (t1.b=t2.b);

1. 物理执行流程详解

在物理层面,该操作不再是"点对点"的查找,而是一次大规模的"内存对撞"过程 :

  1. 扫描并装载驱动表
    MySQL 首先全表扫描驱动表 t1t1t1,将这 100 行数据全部读入线程内存 join_buffer 中 。
    • 注意 :由于是 select *,整行数据都会被存入内存 。
  2. 顺序扫描被驱动表
    随后,MySQL 开始全表扫描被驱动表 t2t2t2。它每从磁盘读入 t2t2t2 的一行数据,并不会立即写回结果集 。
  3. 内存高速比对
    对于 t2t2t2 读入内存的每一行,MySQL 都会将其与 join_buffer 中缓存的 100 行 t1t1t1 数据逐一进行等值判断 。
    • 如果匹配成功,则作为结果集的一行返回。
  4. 循环直至结束
    重复上述过程,直到 t2t2t2 的 1000 行数据全部扫描并比对完毕。

2. 核心物理指标

在这个示例中,虽然 SQL 看似简单,但底层的物理开销非常巨大 :

  • 磁盘扫描行数 :100+1000=1100100 + 1000 = 1100100+1000=1100 行 。
    • 驱动表 t1t1t1 扫一遍,被驱动表 t2t2t2 扫一遍。
  • 内存判断次数 :100×1000=10100 \times 1000 = \mathbf{10}100×1000=10 万次
    • 这是最消耗 CPU 资源的地方。因为没有索引辅助,每一行都必须和内存中所有的行"对撞"一次。

3. 极端情况:如果 join_buffer 不够大怎么办?

如果驱动表 t1t1t1 很大(例如 10 万行),而 join_buffer 只能放得下 1 万行,物理流程会演变为分段处理(Block)

  1. 从 t1t1t1 取出前 1 万行放入 join_buffer
  2. 扫描 t2t2t2 全表(100 万行),在内存中进行 1万×100万1\text{万} \times 100\text{万}1万×100万 次比对 。
  3. 清空 join_buffer
  4. 从 t1t1t1 取出接下来的 1 万行,再次全表扫描 t2t2t2 进行比对 。

物理后果:

此时,被驱动表 t2t2t2 会被重复扫描多次 。如果 t1t1t1 被分成 KKK 段,总扫描行数将变成 N+K×MN + K \times MN+K×M 。这会产生剧烈的磁盘 IO 波动,并严重污染 Buffer Pool,导致整个数据库实例响应变慢。


二、关联字段在其中一个表中有索引

join 关联字段在两个表中呈现"一有一无"的索引状态时,MySQL 的物理执行过程取决于优化器对驱动表(Driver Table)的选择

核心结论是:MySQL 优化器会为了利用索引而极力避免 BNL 算法 ,通常会选择将带索引的表作为被驱动表

1. 物理流程的两种可能性

假设 表 t1t1t1 无索引 (100 行),表 t2t2t2 有索引(1000 行)。

情况一:带索引的表 t2t2t2 作为"被驱动表"(这是最优选)

此时物理上执行的是 Index Nested-Loop Join (NLJ) 算法 :

  1. 扫描驱动表 :全表扫描不带索引的 t1t1t1(100 行)。
  2. 点对点查找 :每读出 t1t1t1 的一行,就拿着关联字段的值去 t2t2t2 的索引树上搜索。
  3. 物理开销
    • 扫描行数 :100+100100 + 100100+100(假设 1:1 匹配)。
    • 计算复杂度 :100×log⁡21000100 \times \log_2 1000100×log21000。
  4. 结果:这是最快的方式,因为利用了索引的对数级搜索能力。
情况二:不带索引的表 t1t1t1 作为"被驱动表"(这是最差选)

如果因为某些极端原因(如 t2t2t2 过滤后的结果集极小),优化器选了 t1t1t1 倒过来驱动,则物理上执行的是 Block Nested-Loop Join (BNL) 算法 :

  1. 扫描驱动表 :扫描带索引的 t2t2t2,将数据放入 join_buffer

  2. 暴力对撞 :由于被驱动表 t1t1t1 没有索引,系统必须对 t1t1t1 执行全表扫描。

  3. 内存比对 :将 t1t1t1 的每一行与内存中的数据进行暴力比对。

  4. 物理开销

    • 内存判断次数 :1000×100=101000 \times 100 = \mathbf{10}1000×100=10 万次
  5. 结果:性能极差,且会大量消耗 CPU 资源 。

2. 优化器的"智取":索引权重高于表大小

在之前的讲解中,我们提到过"小表驱动大表"的原则 。但在"一有一无"的情况下,是否有索引 对算法效率的影响(NLJ vs BNL)通常远超表的大小

具体示例:

  • 表 AAA :100 行,没有索引
  • 表 BBB :100 万行,有索引

虽然表 AAA 很小,但如果选 AAA 驱动 BBB,可以走索引(NLJ);如果选 BBB 驱动 AAA,则必须走暴力内存比对(BNL)。

  • A 驱动 B (NLJ) :扫描 100 行 + 100 次索引树搜索 →\rightarrow→ 毫秒级完成
  • B 驱动 A (BNL) :扫描 100 万行 + 1 亿次内存比对 →\rightarrow→ 秒级甚至分钟级

因此,物理交互的真相是: 优化器会优先选择那个不带索引的表作为驱动表 ,从而强行让那个带索引的表成为被驱动表,以触发 NLJ 或 BKA 算法来提升性能 。


三、BKA优化NLJ算法

BKA(Batched Key Access)算法是 MySQL 对 Index Nested-Loop Join(NLJ)的进一步优化。它的核心逻辑是利用 join_buffer 批量缓存驱动表的数据,并配合 MRR(Multi-Range Read) 技术,将原本随机的磁盘访问转化为顺序访问。

以下是关于 BKA 算法的详细拆解:

1. 适用场景

BKA 算法主要适用于以下场景:

  • 使用了索引关联:即被驱动表的关联字段上有索引(这是 NLJ 算法的前提)。

  • 磁盘 IO 瓶颈:当被驱动表数据量较大且无法全部加载进内存时,随机回表的代价很高,此时 BKA 的提速效果最显著。

  • 配置开启 :由于 BKA 依赖于 MRR,而 MRR 的开启策略通常较为保守,因此需要手动设置参数以确保 BKA 能够生效:

    sql 复制代码
    set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

2. 物理执行流程

传统的 NLJ 算法是"拿一行驱动表数据,去被驱动表查一次索引"。而 BKA 算法改成了"批量处理":

  1. 批量读入驱动表 :从驱动表 t1 中读取满足条件的行,并存入 join_buffer 内存中。
  2. 构造批量键值 :当 join_buffer 存满或者数据读完后,将这批记录的关联字段(Key)一次性发送给被驱动表 t2 的引擎层。
  3. 触发 MRR 排序 :被驱动表 t2 接收到这批 Key 后,利用 MRR 机制进行如下物理操作:
    • 在普通索引树上找到所有匹配记录的主键 ID。
    • 将这些 ID 放入 read_rnd_buffer 中进行递增排序
    • 按照排序后的主键顺序,顺序访问主键索引(聚簇索引)拉取整行数据。
  4. 返回结果 :将取出的 t2 数据与 join_buffer 中的 t1 原始数据进行匹配,返回给 Server 层。

3. 算法开销分析

BKA 虽然极大地提升了查询速度,但也带来了一定的资源开销:

  • 内存开销
    • join_buffer :用于缓存驱动表的数据,其大小由 join_buffer_size 决定 。
    • read_rnd_buffer :用于 MRR 阶段的主键 ID 排序,大小由 read_rnd_buffer_size 决定。
  • CPU 开销
    • 在 MRR 阶段,系统需要对主键 ID 序列进行排序操作。如果一次性处理的 ID 数量极其庞大,会产生一定的 CPU 计算负担。
  • IO 模式改变(正向开销)
    • 扫描行数不变:物理上扫描的行数与传统的 NLJ 并没有区别 。
    • 性能提升根源 :开销的减少主要源于减少了磁头寻道时间。将随机的磁盘跳转改成了连续的块读取,这在机械硬盘上是量级的提升,在 SSD 上也能减少内部控制器的随机寻址压力。

四、HashJoin优化BNL算法

由于 MySQL 5.6 和 5.7 版本在内核中并未内置原生的 Hash Join 执行引擎,文档主要通过"应用层 Hash Join"来展示其逻辑,以解决被驱动表无索引时 Block Nested-Loop Join (BNL) 算法性能低下的问题。

Hash Join 的核心物理逻辑是将原本需要"暴力比对"的 N×MN \times MN×M 次判断,通过在内存中构建哈希表,降级为近似 O(1)O(1)O(1) 的定位操作。

1. 适用场景

  • 无索引关联:Join 关联字段在两张表中都没有索引,导致无法使用 NLJ 或 BKA 算法。
  • 等值连接 :仅适用于等值 Join(如 t1.b = t2.b),因为哈希表无法处理范围查询(如 ><)。
  • 内存充足:内存(或应用端内存)必须能够容纳较小表(Build Table)过滤后的全部数据集。

2. 物理执行流程

select * from t1 join t2 on t1.b = t2.b 为例,假设 t1t1t1 为 1000 行, t2t2t2 为 100 万行。

1. 构建阶段 (Build Phase)
  1. 扫描小表 :全表扫描驱动表 t1t1t1。
  2. 内存建模:在内存中创建一个哈希表(Hash Table)。
  3. 哈希计算 :将 t1t1t1 的每一行根据关联字段 bbb 的值计算哈希值,然后以 bbb 为 Key,整行数据为 Value 存入哈希表。
2. 探测阶段 (Probe Phase)
  1. 扫描大表 :全表扫描被驱动表 t2t2t2。
  2. 命中检索 :对于 t2t2t2 读入内存的每一行,取其字段 bbb 的值进行同样的哈希计算。
  3. 定位匹配 :直接去哈希表中检索对应的 Key。
    • 匹配成功 :直接取出 t1t1t1 的数据进行拼装,返回结果。
    • 匹配失败 :继续处理 t2t2t2 的下一行。

3. 算法开销分析

1. 扫描行数 (IO 开销)
  • 磁盘扫描 :N+MN + MN+M。
  • 优势:每个表都只做一次物理全表扫描,彻底规避了 BNL 算法中被驱动表可能被重复扫描多次的问题 。
2. CPU 计算开销
  • 复杂度 :由 BNL 的 O(N×M)O(N \times M)O(N×M) 暴力比对降级为 O(N+M)O(N + M)O(N+M)
  • 物理意义:内存中进行的不再是数亿次的字符串或数值比较,而是极快且次数极少的哈希计算与指针定位。
3. 内存开销
  • 空间占用:必须在内存中开辟足以存储整个驱动表(Build表)结果集的空间。
  • 风险:如果结果集超过内存上限,会导致严重的内存溢出(OOM)或必须借用磁盘临时文件进行分块处理(Graceful Hash Join),这会引入额外的磁盘 IO 损耗 。

4. 具体示例对比

背景 : t1t1t1(1000行), t2t2t2(100万行),字段 bbb 均无索引。

指标 Block Nested-Loop (BNL) Hash Join (应用层)
物理逻辑 扫描 t2t2t2 时,每一行都要跟内存里的 1000 行 t1 比对 扫描 t2t2t2 时,每一行只算一次哈希,直接定位 t1t1t1
内存判断次数 10 亿次 (1000×100万1000 \times 100万1000×100万) 100 万次 哈希查找
执行耗时 71.95 秒 < 1 秒

总结:Hash Join 的物理精髓是"空间换时间"。它通过在内存中提前构建高效的索引结构(哈希表),消除了 Join 过程中最昂贵的笛卡尔积式暴力比对。

相关推荐
Java面试题总结1 小时前
MySQL 反模式与排查宝典
数据库·mysql
STARFALL0011 小时前
MySQL 运维
运维·数据库·mysql
XD7429716361 小时前
科技早报晚报|2026年5月14日:数据库沙箱、文档解析与 GPU 共享,今天更值得做成产品的 3 个技术机会
数据库·科技·开源项目·开发者工具·ai基础设施
祀爱1 小时前
ASP.NET Core 集成NLog详细教程
数据库·后端·asp.net
醇氧1 小时前
CentOS 7 安装 MySQL 8.0.28 el7 (完美兼容 OpenSSL 1.1)
linux·mysql·centos
java修仙传1 小时前
Java 实习日记:一次 Excel 导入校验 Bug 的定位与数据更新逻辑优化
java·数据库·bug·excel·后端开发
wa的一声哭了1 小时前
Mit6.s081 Interrupts and device driver(中断和设备驱动)
linux·服务器·arm开发·数据库·python·gpt·算法
码界筑梦坊1 小时前
117-基于Python的印度犯罪数据可视化分析系统
开发语言·python·mysql·信息可视化·毕业设计·echarts·fastapi
RoboWizard1 小时前
DIY移动硬盘?2230能否堪大任!
数据库·人工智能·智能手机·性能优化·负载均衡