文章目录

MySQL Join查询的Nested Loop和Hash Join执行方式对比

发布于 2026-05-16 15:16:28 · 浏览 10 次 · 评论 0 条

MySQL Join查询的Nested Loop和Hash Join执行方式对比

数据库查询性能低下往往是由于Join算法选择不当导致的。在MySQL中,理解Nested Loop Join(嵌套循环连接)与Hash Join(哈希连接)的区别,是优化SQL语句的关键一步。本文将直接解析这两种机制的工作原理,并通过实际操作演示如何观察和选择它们。


一、 核心机制解析

1. Nested Loop Join (嵌套循环连接)

这是MySQL中最基础、历史最悠久的Join算法。你可以把它想象成两层嵌套的for循环。

  • 基本逻辑:外层循环遍历“驱动表”的每一行数据,对于每一行,去内层循环的“被驱动表”中查找匹配的行。
  • 执行流程
    1. 读取驱动表的第一行。
    2. 根据关联条件,去被驱动表查找数据。
    3. 如果被驱动表有索引,利用索引快速定位(Index Nested Loop);如果没有索引,则全表扫描(Block Nested Loop)。
    4. 重复上述步骤,直到驱动表所有行处理完毕。

当数据量较小,或者被驱动表上有高效索引时,这种算法非常高效。

2. Hash Join (哈希连接)

这是从MySQL 8.0.18版本开始引入的算法,专门用于处理大数据量且无索引关联的场景。它是一种“以空间换时间”的策略。

  • 基本逻辑:将输入表分为“构建输入”(通常是较小的表)和“探测输入”(通常是较大的表)。
  • 执行流程
    1. 构建阶段:读取构建输入的所有行,根据Join Key计算哈希值,并存放在内存中的哈希表里。
    2. 探测阶段:读取探测输入的每一行,同样计算Join Key的哈希值,去哈希表中寻找匹配的数据。

当两张表数据量都很大,且关联字段没有索引时,Hash Join通常比Nested Loop快得多。


二、 执行流程对比图

为了更直观地展示差异,下面使用流程图描述两种算法的数据处理逻辑。

Nested Loop Join 流程

graph TD A["开始: 驱动表 (表A)"] --> B["读取表A一行数据"] B --> C{被驱动表 (表B) 有索引?} C -- 是 --> D["通过索引查找表B"] C -- 否 --> E["全表扫描表B"] D --> F["组合结果集"] E --> F F --> G{表A还有数据?} G -- 是 --> B G -- 否 --> H["结束: 返回结果"]

Hash Join 流程

graph TD subgraph Build ["构建阶段"] A["读取小表 (构建输入)"] --> B["计算Join Key的哈希值"] B --> C["存入内存哈希表"] end subgraph Probe ["探测阶段"] D["读取大表 (探测输入)"] --> E["计算Join Key的哈希值"] E --> F["去哈希表探测匹配"] F --> G{匹配成功?} G -- 是 --> H["组合结果集"] G -- 否 --> I["丢弃"] end C --> D H --> J{大表读完?} J -- 否 --> D J -- 是 --> K["结束: 返回结果"]

三、 实战:如何观察与切换算法

通过以下步骤,你可以亲自动手观察MySQL在不同场景下如何自动选择或强制使用这两种算法。

1. 准备测试环境

执行以下SQL脚本,创建两张无索引的大表,模拟Hash Join的典型场景。

-- 创建表 t1 (作为小表)
CREATE TABLE t1 (
    id INT PRIMARY KEY,
    col_a INT,
    filler CHAR(100)
);

-- 创建表 t2 (作为大表)
CREATE TABLE t2 (
    id INT PRIMARY KEY,
    col_b INT,
    filler CHAR(100)
);

-- 插入测试数据 (注意: 生产环境请勿随意插入大量数据)
-- 这里假设使用存储过程或脚本插入数据
-- t1 插入 1000 条
INSERT INTO t1 SELECT seq, seq % 100, 'test' FROM seq_1_to_1000;
-- t2 插入 10000 条
INSERT INTO t2 SELECT seq, seq % 100, 'test' FROM seq_1_to_10000;

2. 观察 Hash Join (默认行为,MySQL 8.0.18+)

在较新版本的MySQL中,优化器通常会在没有索引的情况下自动选择Hash Join。

执行 EXPLAIN 命令查看执行计划:

EXPLAIN FORMAT=TREE
SELECT * FROM t1 JOIN t2 ON t1.col_a = t2.col_b;

检查输出结果中的关键字:

  • 输出内容会包含 Inner hash join 字样。
  • 你会看到 t1 作为 build input,t2 作为 probe input。

3. 强制使用 Nested Loop Join

为了对比性能,我们需要禁用 Hash Join,强制数据库回退到 Nested Loop。

执行以下命令关闭 Hash Join 开关:

SET SESSION optimizer_switch = 'hash_join=off';

再次执行查询并观察:

EXPLAIN FORMAT=TREE
SELECT * FROM t1 JOIN t2 ON t1.col_a = t2.col_b;

检查输出结果:

  • hash_join=off 生效后,执行计划将变为 Nested loop inner join
  • 在没有索引的情况下,你可能会看到 Block Nested Loop 的警告或执行路径,这意味着性能将急剧下降,因为算法复杂度从接近线性 $O(N)$ 变成了乘积级 $O(N \times M)$。

4. 恢复默认设置

测试完成后,执行命令恢复优化器默认设置:

SET SESSION optimizer_switch = 'hash_join=on';

四、 核心差异总结表

为了方便记忆和查阅,以下是两种算法在关键维度的对比。

特性 Nested Loop Join Hash Join
适用场景 驱动表数据量小,或被驱动表有高效索引 大表关联大表,且关联字段无索引
时间复杂度 $O(R \times S)$ 或 $O(R \times \log S)$ 约 $O(R + S)$ (构建+探测)
内存消耗 较低 (主要依赖磁盘I/O) 较高 (需在内存构建Hash表)
是否依赖索引 是 (极度依赖索引提升性能) 否 (完全不需要索引)
MySQL版本支持 所有版本 8.0.18 及以上版本

五、 优化建议

在实际工作中,根据数据特征选择正确的算法至关重要。

  1. 优先利用索引:如果关联条件上有索引,Nested Loop Join(特别是 Index Nested Loop)通常是最高效的,因为它避免了全表扫描和内存构建开销。
  2. 关注大数据量无索引场景:当执行大表Join且发现 EXPLAIN 中出现 Block Nested Loop 时,应检查是否为MySQL 8.0以下版本,或显式开启 hash_join
  3. 监控内存使用:Hash Join需要将构建表加载到内存。如果内存不足(join_buffer_size 不够),MySQL会将数据分片写到磁盘上,这会导致性能骤降。此时应调整参数 join_buffer_size 或限制查询数据量。
  4. 利用Hint控制:在极端情况下,如果优化器选择错误,可以使用 Hint 强制指定算法:
    • 强制 Hash Join: SELECT /*+ HASH_JOIN(t1, t2) */ ...
    • 强制 Nested Loop: SELECT /*+ NO_HASH_JOIN(t1, t2) */ ...

通过理解这两者的底层逻辑,你能够更精准地诊断慢查询原因,并通过调整索引或配置参数来显著提升查询效率。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文