【MySQL百日打怪升级第10天】JOIN的底层原理与优化:NLJ、Hash Join 与 Merge Join

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS Agent(兼容OpenClaw),2核4GB
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 本文系统解析MySQL三大JOIN算法:NLJ(含Simple/Index/Block变体)、8.0.18引入的Hash Join(O(N+M)复杂度,专治无索引大表连接),以及面试常考但MySQL原生不支持的Sort-Merge Join,附实战EXPLAIN识别与优化指南。(239字)

JOIN的底层原理与优化:NLJ、Hash Join 与 Merge Join


大家好,我是一名拥有10年以上经验的DBA老兵。

做这个系列,源于一个朴素的愿望:把踩过的坑、总结的经验系统化输出,希望能帮到刚入行或想进阶的兄弟们。

让我们开始今天的第10天内容。


🎯 面试考点

  • MySQL 有哪些 JOIN 算法?各自适用什么场景?
  • Nested Loop Join 的三种变体有什么区别?
  • Hash Join 是在 MySQL 哪个版本引入的?解决了什么问题?
  • 驱动表怎么选?小表驱动大表的原理是什么?
  • EXPLAIN 怎么看 JOIN 用的是哪种算法?

背景引入

💡 说白了:JOIN 慢,不是 SQL 写得有问题,是你没搞懂 MySQL 是怎么"连接"两张表的

前几天一个朋友跟我说,他线上有个 JOIN 查了 30 秒,开发提了好几次优化,他调了索引、改了 SQL,就是不见效。

我说你 EXPLAIN 贴我看看。

一看就明白了——Using join buffer (Block Nested Loop),两个表都是全表扫描,100 万 × 500 万,硬碰硬。

"你 MySQL 什么版本?"

"5.7。"

"在 8.0 上试试。"

他半信半疑地先在测试环境升了级,什么都没改,同一个 SQL,从 30 秒变成了 2 秒。

(后来他请我喝了顿酒,说早知道升级这么管用,之前一个月的调优都白干了。)

⚠️ 认真说两句:因为一条 SQL 慢就升级整个数据库,在真实生产环境里不太现实。大多数情况下,优先方案是加索引——有索引走 Index NLJ,绝大多数 JOIN 慢的问题都能解决。升级版本是"终极大招",适合本身就计划做版本升级的团队。这个故事之所以走了升级路线,是因为那俩表的 JOIN 列确实加不了有效索引(业务需求所限),升级成了最后的出路。

说实话,MySQL 的 JOIN 算法,是有"代差"的。5.7 和 8.0 在 JOIN 这个能力上,差了一个时代。

今天的目标:把 MySQL 的三种 JOIN 算法一次讲透,面试必问、工作必用。


核心概念

Nested Loop Join:MySQL 的"默认选项"

简单说:拿表 A 的每一行,去表 B 里找匹配。就像你用一本书的目录找内容——翻到第一页,去另一本书里找对应的章节,翻完一整本,再回来翻第二页。

Nested Loop Join 有三个版本,从简单到高级:

版本一:Simple NLJ(纯粹硬扫)

-- 伪代码,方便理解
for each row in table_A:          -- 外层循环
    for each row in table_B:      -- 内层循环
        if row_A.id = row_B.id:   -- 匹配条件
            return row_A + row_B

两层嵌套循环,复杂度 O(N×M)。表 A 100 行 × 表 B 1000 行 = 10 万次比较。数据量一上去,直接爆炸。

版本二:Index NLJ(用索引加速内层)

MySQL 发现内层表有索引,就不用全表扫了——直接走索引找匹配行。

-- 伪代码
for each row in table_A:
    lookup_index(table_B, key=row_A.id)  -- 走 B+ 树,O(log M)

复杂度从 O(N×M) 降到 O(N×logM),这才是"小表驱动大表"的真正含义。

面试必问

Q: 什么是"小表驱动大表"?

不是说把小表放左边、大表放右边。而是让优化器选择外层循环次数更少的执行计划。外层循环每增加一行,内层就多执行一次完整的索引查找。所以外层越少越好——这就是"小表驱动大表"的底层原理。

说人话:谁当外层,谁就决定了内层要执行多少次。外层是 100 行还是 100 万行,天差地别。

版本三:Block NLJ(批处理版本)

当内层表没有索引时,MySQL 不会真的一次一次扫全表。它用 join_buffer_size 把外层表的一批行缓存起来,一次性传给内层表扫描。

join_buffer_size 默认:256KB
每次缓存:若干行外层数据 → 一次性传给内层表扫描
-- 查看 join buffer 大小
SHOW VARIABLES LIKE 'join_buffer_size';

面试必问

Q: Block NLJ 比 Simple NLJ 好在哪?

Simple NLJ 每取外层一行,就要扫一遍内层全表。Block NLJ 先把外层的一批行攒起来(比如攒 100 行),然后用这批行去扫一次内层表。内层表的扫描次数从 N 次降到了 N/批次大小 次。join_buffer_size 越大,批次越大,扫描次数越少。

Hash Join:MySQL 8.0 的"核武器"

简单说:把一张表的所有 JOIN 列算成哈希码存在内存里,另一张表逐行去这个哈希表里查。就像你有一串钥匙,你先把所有锁的"指纹"记在脑子里,然后拿每把钥匙去脑子里匹配——不用一把一把试了。

-- 伪代码
hash_table = {}
for each row in small_table:
    hash_table[hash(row.join_key)] = row     -- 建哈希表

for each row in big_table:
    match = hash_table.lookup(hash(row.join_key))  -- 探测
    if match:
        return match + row

Hash Join 是 MySQL 8.0.18 在 JOIN 方面最大的变革。

在 5.7 及之前,如果 JOIN 的两张表都没有可用索引,MySQL 只能走 Block NLJ——不管 join_buffer_size 调多大,还是要一遍一遍扫内层表。

8.0.18 引入 Hash Join 之后,这个问题直接没了。

对比项 Block NLJ (MySQL 5.7 / 8.0 < 8.0.18) Hash Join (MySQL 8.0.18+)
原理 批量缓存 + 全表扫描 哈希表 + 内存探测
时间复杂度 O(N×M) → O(N×M/批次) O(N+M)
内存使用 join_buffer_size join_buffer_size(建哈希表)
适用场景 小表 + 无索引 大表 + 无索引 时碾压
嵌套循环 需要 不需要

同一个 SQL,5.7 跑 30 秒,8.0 跑 2 秒,差的就是这个 Hash Join。

MySQL 什么时候会用 Hash Join?

-- ✅ 会使用 Hash Join 的场景:
-- 1. 等值 JOIN,且没有可用索引
SELECT * FROM t1 JOIN t2 ON t1.id = t2.id;

-- 2. 内连接、左连接、右连接(等值条件)
SELECT * FROM orders o LEFT JOIN users u ON o.user_id = u.id;

-- ❌ 不会使用 Hash Join 的场景:
-- 1. 非等值 JOIN(如 >、<、BETWEEN)
SELECT * FROM t1 JOIN t2 ON t1.id > t2.id;
-- 2. 有可用索引(MySQL 会优先选 Index NLJ)
SELECT * FROM t1 JOIN t2 ON t1.id = t2.id;  -- 如果 t2.id 有索引

面试必问

Q: Hash Join 一定是更好的吗?

不一定。Hash Join 的两大限制:一是只能做等值 JOIN,非等值条件用不了;二是哈希表要占用内存,如果小表数据量超大,哈希表可能溢出到磁盘,反而比 NLJ 慢。一般来说,NLJ 在有索引时更优,Hash Join 在无索引大表 JOIN 时碾压。


Sort-Merge Join:面试爱问,MySQL 很少用

简单说:先把两张表按 JOIN 列排好序,然后用两个指针像拉链一样"合并"在一起。

排好序的表 A:1, 3, 5, 7, 9
                ↑
排好序的表 B:1, 2, 3, 4, 5
                ↑

指针从头开始比:
A[1] = B[1] → 匹配,输出
A[3] > B[2] → B 指针前进
A[3] = B[3] → 匹配,输出
A[5] > B[4] → B 指针前进
A[5] = B[5] → 匹配,输出

复杂度 O(NlogN + MlogM + N+M),排序是大头。

MySQL 有 Sort-Merge Join 吗?

MySQL 没有像 PostgreSQL 那样的原生 sort-merge join 实现。但在某些场景下,如果两张表都按 JOIN 列有序(比如有索引),优化器可能会利用这个有序性做"类 Merge Join"的访问方式。

面试官问到这个,你就说:MySQL 主要用 Nested Loop Join 和 Hash Join,Merge Join 是其他数据库(如 PostgreSQL)常用的算法,理解其原理有助于面试对比。


实战案例

场景一:看 EXPLAIN 识别 JOIN 算法

准备工作

以下所有 EXPLAIN 输出基于 同一个表结构、同一批数据,跨 MySQL 8.0.13 和 8.0.22 两个版本实测。

-- 建表(无索引)
CREATE TABLE t1 (id INT, val VARCHAR(100));
CREATE TABLE t2 (id INT, val VARCHAR(100));

-- 插入数据(各 1000 行)
INSERT INTO t1 WITH RECURSIVE cte AS (
    SELECT 1 AS n UNION ALL SELECT n+1 FROM cte WHERE n < 1000
) SELECT n, CONCAT('val_', n) FROM cte;

INSERT INTO t2 WITH RECURSIVE cte AS (
    SELECT 1 AS n UNION ALL SELECT n+1 FROM cte WHERE n < 1000
) SELECT n, CONCAT('val_', n) FROM cte;

ANALYZE TABLE t1, t2;

① Block NLJ — MySQL 8.0.13(等效 5.7 行为)

EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.id = t2.id;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |   100.00 | NULL                                               |
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+

Using join buffer (Block Nested Loop) — 5.7 / 8.0.0~8.0.17 无索引 JOIN 的"标配"。

② Hash Join — MySQL 8.0.22(8.0.18+)

EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.id = t2.id;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |   100.00 | NULL                                       |
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |    10.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+

Extra 从 Block Nested Loop 变成 hash join,这就是 8.0.18+ 的核心变化。同一条 SQL 同一批数据,优化器自动选择 Hash Join。

FORMAT=TREE 的树状输出更直观:

-> Inner hash join (t1.id = t2.id)  (cost=101101.00 rows=100000)
    -> Table scan on t1  (cost=101.00 rows=1000)
    -> Hash
        -> Table scan on t2  (cost=101.00 rows=1000)

③ Index NLJ — 加索引后(MySQL 8.0.22)

CREATE INDEX idx_t2_id ON t2(id);
ANALYZE TABLE t2;
EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.id = t2.id;
+----+-------------+-------+------------+------+---------------+-----------+---------+---------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key       | key_len | ref           | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+-----------+---------+---------------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL      | NULL    | NULL          | 1000 |   100.00 | Using where |
|  1 | SIMPLE      | t2    | NULL       | ref  | idx_t2_id     | idx_t2_id | 5       | test.t1.id    |    1 |   100.00 | NULL        |
+----+-------------+-------+------------+------+---------------+-----------+---------+---------------+------+----------+-------------+

type 从 ALL 变成 ref,rows 从 1000 降为 1——这就是索引的威力。

FORMAT=TREE:

-> Nested loop inner join  (cost=451.00 rows=1000)
    -> Filter: (t1.id is not null)  (cost=101.00 rows=1000)
        -> Table scan on t1  (cost=101.00 rows=1000)
    -> Index lookup on t2 using idx_t2_id (id=t1.id)  (cost=0.25 rows=1)

④ 非等值 JOIN — 有索引也走不了 Index NLJ(MySQL 8.0.22)

EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.id > t2.id;
+----+-------------+-------+------------+------+---------------+-----------+------+---------+------+----------+------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key       | key_len | ref    | rows | filtered | Extra                                          |
+----+-------------+-------+------------+------+---------------+-----------+------+---------+------+----------+------------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL      | NULL    | NULL   | 1000 |   100.00 | NULL                                           |
|  1 | SIMPLE      | t2    | NULL       | ALL  | idx_t2_id     | NULL      | NULL    | NULL   | 1000 |    33.33 | Range checked for each record (index map: 0x1) |
+----+-------------+-------+------------+------+---------------+-----------+------+---------+------+----------+------------------------------------------------+

注意 Extra 字段:Range checked for each record (index map: 0x1)——虽然 t2 有索引,但非等值条件无法直接走 ref,MySQL 被迫对 t1 的每一行都重新做一次索引范围扫描。这就是"每行重新规划"的含义。

FORMAT=TREE 解释得更清楚:

-> Nested loop inner join  (cost=100102.78 rows=333300)
    -> Table scan on t1  (cost=101.00 rows=1000)
    -> Filter: (t1.id > t2.id)  (cost=0.04 rows=333)
        -> Index range scan on t2 (re-planned for each iteration)  (cost=0.04 rows=1000)

re-planned for each iteration — 这是非等值 JOIN 性能差的核心原因。

⑤ NO_BNL 禁用 Hash Join(MySQL 8.0.22,无索引)

8.0.20+ 删除了 hash_joinblock_nested_loop 优化器开关,只能通过 hint 局部控制:

EXPLAIN SELECT /*+ NO_BNL(t1, t2) */ * FROM t1 JOIN t2 ON t1.id = t2.id;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |   100.00 | NULL        |
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

对比不加 hint 时第②项的 Hash Join——Extra 的 hash join 消失了,退化为纯 Nested Loop(两个 type=ALL,无 join buffer)。FORMAT=TREE:

-> Nested loop inner join  (cost=101101.00 rows=100000)
    -> Table scan on t1  (cost=101.00 rows=1000)
    -> Filter: (t2.id = t1.id)  (cost=1.01 rows=100)
        -> Table scan on t2  (cost=1.01 rows=1000)

⑥ LEFT JOIN(MySQL 8.0.22,有索引)

EXPLAIN FORMAT=TREE SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id;
-> Nested loop left join  (cost=451.00 rows=1000)
    -> Table scan on t1  (cost=101.00 rows=1000)
    -> Index lookup on t2 using idx_t2_id (id=t1.id)  (cost=0.25 rows=1)

和 INNER JOIN 的 Index NLJ 基本一致,只是 inner join 变成了 left join。MySQL 对 LEFT JOIN 的索引选择逻辑是相同的。


场景二:EXPLAIN FORMAT=TREE 怎么看(MySQL 8.0.16+)

从 8.0.16 开始,MySQL 支持 EXPLAIN FORMAT=TREE,以树状结构展示执行计划。上面场景一各小节都附了 FORMAT=TREE 输出,这里集中对比四种算法的树形差异:

JOIN 场景 FORMAT=TREE 关键特征
Hash Join(无索引) Inner hash join + Hash 子树
Index NLJ(有索引) Nested loop inner join + Index lookup on ... using ...
非等值 JOIN Nested loop inner join + Index range scan on ... (re-planned for each iteration)
NO_BNL 禁用 Hash Join Nested loop inner join + Table scan on t2(退化全表)

📌 一个值得注意的差异:8.0.13(8.0.18 之前)的 FORMAT=TREE 在无索引时也会显示 Inner hash join,但实际执行器走的还是 Block NLJ。这是 optimizer 层和 executor 层之间的差异——optimizer 在规划时用了 hash join 的成本模型(代码路径已合并),但 executor 没有 Hash Join 的实现。8.0.18 之后,FORMAT=TREE 才能真正反映实际执行的 JOIN 算法。

场景三:强制控制 JOIN 算法(MySQL 8.0.20+)

8.0.20 删除了 hash_joinblock_nested_loop 优化器开关,只能通过 hint 控制:

-- 有索引时强制用 Hash Join(默认会选 Index NLJ,因为更快)
SELECT /*+ HASH_JOIN(t1, t2) */ * 
FROM t1 JOIN t2 ON t1.id = t2.id;

-- 无索引时禁用 Hash Join,退化为纯 Nested Loop
SELECT /*+ NO_BNL(t1, t2) */ * 
FROM t1 JOIN t2 ON t1.id = t2.id;

⚠️ NO_BNL hint 在 8.0.20+ 的实际作用是禁用 Hash Join(因为 BNL 算法已被移除,Hash Join 占据了它的生态位)。场景一第⑤项有实测对比。

一般不建议强制 hint——相信优化器,除非你 EXPLAIN 确认选错了。


避坑指南

⚠️ 真实踩过的坑:

  1. MySQL 5.7 / 8.0 (< 8.0.18) 的 JOIN 大表无索引,就是灾难

    • Block NLJ 再优化也是全表扫描,只是少扫几次的事
    • 建议:要么加索引转为 Index NLJ,要么升到 8.0.18+ 用 Hash Join
  2. join_buffer_size 调再大也救不了

    • join_buffer_size 默认 256KB,调大到 1MB 甚至 8MB 能缓解 Block NLJ
    • 但它只是"少扫几次",不是"不扫"——治标不治本
    • 建议:优先加索引/升级版本,调 buffer 是下策
  3. Hash Join 不是银弹

    • 如果驱动表特别大(比如几百万行),哈希表可能溢写到磁盘
    • 这时候性能反而不如加个索引走 NLJ
    • 建议:EXPLAIN 确认是 Hash Join 后,看看 rows 是不是合理,不合理就强制 NLJ
  4. ON 条件列类型不一致,Hash Join 可能失效

    • 虽然 MySQL 8.0 做了很多优化,但类型不一致会导致隐式转换
    • 在某些版本中,隐式转换会导致 Hash Join 回退成 NLJ
    • 建议:ON 条件的列类型保持完全一致
  5. 8.0.20+ 关不掉 Hash Join?那是你姿势不对

    • 8.0.20+ 删除了 hash_joinblock_nested_loop 两个优化器开关,Hash Join 成为无索引 JOIN 的唯一算法
    • SET optimizer_switch='hash_join=off' 会收到 warning 并被静默忽略,Hash Join 照跑不误(你 show warnings 看到的 "skip invalid flag" 就是这个意思)
    • 如果需要局部禁用 Hash Join(比如验证性能),用 hint:SELECT /*+ NO_BNL(t1, t2) */ * ...(没错,8.0.20+ 的 NO_BNL hint 实际作用是禁用 Hash Join)
    • 要完整测试 Block NLJ 行为,降级到 8.0.13~8.0.17 或直接用测试环境的多版本集群

如何判断你的 JOIN 用的是哪种算法

💡 三步法:

第一步:EXPLAIN 看 Extra 字段
├── "Using join buffer (hash join)" → Hash Join
├── "Using join buffer (Block Nested Loop)" → Block NLJ
├── Extra 为 NULL,type 为 ref/eq_ref → Index NLJ
└── type 为 ALL,Extra 无 join buffer → Simple NLJ(极少见)

第二步:MySQL 版本
├── 5.7 及以下:没有 Hash Join,最大是 Block NLJ
├── 8.0.0 - 8.0.17:依然没有 Hash Join,跟 5.7 一样走 Block NLJ
├── 8.0.18 - 8.0.19:引入 Hash Join,**这是 JOIN 能力的质变版本**
└── 8.0.20+:Hash Join 进一步优化,移除 hash_join 优化器开关(始终可用)

💡 AI 辅助实战

如果你在本系列第7天(2026-05-02)的文章中创建了 sql-optimizer SKILL,可以把以下内容补充进去,让它在 JOIN 分析上更有用。

sql-optimizer SKILL 中补充以下模块:

### JOIN-Specific Diagnosis

When analyzing slow JOIN queries, follow these steps:

**Step 1 — Identify the JOIN algorithm from EXPLAIN:**

| Extra 字段 | JOIN 算法 | 诊断 |
|-----------|----------|------|
| `Using join buffer (hash join)` | Hash Join | MySQL 8.0.18+, 无索引时自动使用。检查 rows 是否合理 |
| `Using join buffer (Block Nested Loop)` | Block NLJ | 5.7 / 8.0 < 8.0.18 的兜底方案。优先加索引 |
| Extra 为 NULL + type=ref | Index NLJ | 正常,索引生效 |
| `Range checked for each record (index map: 0x1)` | 非等值重规划 | >/< 条件导致每条重新范围扫描 |
| type=ALL×2 + 无 join buffer | 退化 NL | 大概率 NO_BNL hint 禁用了 Hash Join |

**Step 2 — Check MySQL version:**

| 版本 | Hash Join 支持 | 建议 |
|------|---------------|------|
| 5.7 / 8.0.0~8.0.17 | ❌ 不支持 | 加索引或升级 |
| 8.0.18~8.0.19 | ✅ 有开关 | 可用 hash_join=on/off |
| 8.0.20+ | ✅ 始终可用 | 开关已移除,用 `/*+ NO_BNL */` 禁用 |

**Step 3 — Use FORMAT=TREE to verify:**

Hash Join:       -> Inner hash join ... -> Hash -> Table scan
Index NLJ:       -> Nested loop ... -> Index lookup using ...
Non-equi JOIN:   -> Index range scan ... (re-planned for each iteration)
NO_BNL disabled: -> Nested loop ... -> Table scan (no join buffer)

**Optimization priority:** 加索引 > 升级到 8.0.18+ > 调 join_buffer_size

**Output Example:**

Query: SELECT * FROM t1 JOIN t2 ON t1.id = t2.id
   - MySQL version: 8.0.13 (pre-Hash-Join)
   - Algorithm: Block Nested Loop
   - Issue: Both tables scanned fully (type=ALL, rows=1000×1000)
   - Suggestion 1: Add index on t2.id → Index NLJ
   - Suggestion 2: Upgrade to 8.0.18+ → Hash Join
   - Suggestion 3: Last resort — increase join_buffer_size

思考题

🤔 互动时间:

  1. 如果两张表各有 100 万行,JOIN 列都没有索引,MySQL 5.7 和 8.0 在性能上会差多少?你做过实测吗?
  2. EXPLAIN FORMAT=TREE 在 MySQL 8.0.18+ 可以看到 JOIN 算法的详细信息。你知道怎么看吗?
  3. 业务上有一个三表 JOIN,explain 显示两个 join buffer。这种情况怎么优化?

总结

🎯 面试考点

  • Nested Loop Join:MySQL 最基础的 JOIN 算法,分 Simple/Index/Block 三种
  • Index NLJ = 有索引时的最优选择,O(N×logM)
  • Block NLJ = 无索引时 5.7 / 8.0(<8.0.18) 的兜底方案,批处理减少扫描次数
  • Hash Join = MySQL 8.0.18+ 引入,无索引大表 JOIN 时碾压 Block NLJ
  • Sort-Merge Join = 面试常问、MySQL 不常用,理解原理即可
  • 驱动表选择:本质是让外层循环次数更少,"小表驱动大表"
  • 优化优先级:加索引 > 升级版本 > 调 join_buffer_size

🎯 今日行动

现在就做一件事:打开你的数据库,找一个你觉得慢的 JOIN 查询,跑一下 EXPLAIN,看看 Extra 字段里写的是什么——

  • 看到 hash join 了?说明你的 MySQL 8.0.18+ 正在帮你兜底
  • 看到 Block Nested Loop 了?该加索引或者该考虑升级了
  • 看到 ref 了?恭喜,索引 NLJ 正常工作

截图发到评论区,明天我挑几个典型的来分析。


下期预告:子查询的坑与优化方案 —— 面试必问!
全本合集《每天一个MySQL知识点,百日打怪升级》


有问题欢迎评论区交流,明天见!

相关文章
|
10天前
|
SQL 关系型数据库 MySQL
EXPLAIN 执行计划:一眼看穿你的SQL慢在哪
数据库小学妹带你轻松掌握SQL性能诊断!通过EXPLAIN查看执行计划,精准识别索引失效、全表扫描(ALL)、key为NULL等瓶颈。聚焦type、key、rows等6个关键字段,结合实战案例与避坑指南(如函数滥用、最左前缀破坏),让优化有的放矢。学完即用,告别盲目调优!
|
人工智能 数据可视化 安全
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
本文详解如何用阿里云Lighthouse一键部署OpenClaw,结合飞书CLI等工具,让AI真正“动手”——自动群发、生成科研日报、整理知识库。核心理念:未来软件应为AI而生,CLI即AI的“手脚”,实现高效、安全、可控的智能自动化。
35064 57
|
3月前
|
人工智能 API 机器人
OpenClaw 用户部署和使用指南汇总
本文档为OpenClaw(原MoltBot)官方使用指南,涵盖一键部署(阿里云轻量服务器年仅68元)、钉钉/飞书/企微等多平台AI员工搭建、典型场景实践及高频问题FAQ。同步更新产品化修复进展,助力用户高效落地7×24小时主动执行AI助手。
29185 253
|
2天前
|
测试技术 API 数据处理
Claude API 接入方案解析:国内业务落地要关注哪些限制
Claude API 的基础接入并不复杂,但企业落地不能只看 Demo。模型版本、地区限制、网络链路、限流策略和成本治理,都会影响最终稳定性。
109 7
|
22天前
|
人工智能 弹性计算 安全
阿里云免费部署 Hermes Agent 教程:零门槛搭建自进化 AI 智能体
阿里云免费提供Hermes Agent一键部署方案:基于ECS、百炼大模型与计算巢,零代码、几分钟即可搭建开源自进化AI智能体。支持跨会话记忆、多平台接入、私有化部署,兼顾易用性与数据安全,个人提效与企业数字化皆适用。
|
1月前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
25033 65
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
1月前
|
缓存 运维 数据可视化
RedisStudio-en-0.1.5可视化管理工具​安装步骤详解(附Redis可视化与Key管理教程)
RedisStudio-en-0.1.5.exe 是一款轻量级英文版 Redis 可视化管理工具,支持 Win7/10/11(32/64位)。图形化界面简化连接、浏览、搜索、编辑与删除 Key 等操作,告别命令行,提升开发运维效率。(239字)
|
12天前
|
数据采集 缓存 运维
IP查询工具如何评估IP负载?云上资源分配的实战方法
我们曾因P99延迟骤升盲目扩容无效,最终靠IP分桶定位到某云厂商ASN段的爬虫流量。IP查询工具不测性能,而是为请求打标签(ASN/代理类型/风险分等),结合监控数据精准识别“谁拖垮了系统”。分四类桶、设三条件、按优先级调度(分流>限流>扩容>封禁),离线缓存+二次验证,避免误伤。
|
1月前
|
人工智能 IDE 测试技术
Claude Code 编程哲学正在改变一切:从“理解代码”到“跑通代码”
本文剖析Coding Agent范式演进:传统“理解优先”向量方案在真实工程中失效,因代码动态性、理解≠修改、上下文增噪;Claude Code转向“终端调试范式”,以执行反馈驱动多轮试错;CodeGraph仅优化检索,未解修改正确性难题。核心转变是从“看懂代码”到“跑通代码”,标志AI编程进入执行驱动新阶段。