RSS
菜单
全部文章快讯开发科技深度热点

第 9 章 JOIN与多表查询面试精讲

内容摘要

JOIN 是 SQL 查询的高频写法,也是慢查询的重灾区。本篇用 16 道高频面试题讲清 JOIN 类型、Nested Loop Join 原理、驱动表选择、BNL/INLJ、ON 与 WHERE 的区别、LEFT JOIN 退化为 INNER JOIN 的坑、半连接、笛卡尔积,以及 JOIN 性能排查思路。学完本篇,你能解释为什么"JOIN 字段一定要加索引""为什么 LEFT JOIN 后 WHERE 右表字段非空会变成内连接",并能对一条慢 JOIN 给出优化方案。前置知识:索引、EXPLAIN 基础;本系列示例基于 MySQL 8.0。

📖 本篇简介:JOIN 是 SQL 查询的高频写法,也是慢查询的重灾区。本篇用 16 道高频面试题讲清 JOIN 类型、Nested Loop Join 原理、驱动表选择、BNL/INLJ、ON 与 WHERE 的区别、LEFT JOIN 退化为 INNER JOIN 的坑、半连接、笛卡尔积,以及 JOIN 性能排查思路。学完本篇,你能解释为什么"JOIN 字段一定要加索引""为什么 LEFT JOIN 后 WHERE 右表字段非空会变成内连接",并能对一条慢 JOIN 给出优化方案。前置知识:索引、EXPLAIN 基础;本系列示例基于 MySQL 8.0。

目录

  • 1 JOIN 高频考点地图
  • 题目 1MySQL 支持哪些 JOIN 类型
  • 题目 2INNER LEFT RIGHT FULL JOIN 的区别
  • 题目 3MySQL 怎么做 JOINNested Loop Join 原理
  • 题目 4什么是驱动表和被驱动表优化器怎么选
  • 题目 5Index Nested Loop Join 是什么
  • 题目 6Block Nested Loop JoinBNL是什么
  • 题目 7为什么 JOIN 字段要加索引
  • 题目 8LEFT JOIN 为什么有时被优化成 INNER JOIN
  • 题目 9ON 和 WHERE 的区别LEFT JOIN 后过滤右表为什么变内连接
  • 题目 10多表 JOIN 最多几张表合适
  • 题目 11STRAIGHTJOIN 是什么什么时候用
  • 题目 12Semi Join 半连接是什么IN 子查询怎么被优化
  • 题目 13什么是笛卡尔积怎么避免
  • 题目 14JOIN 慢怎么排查和优化
  • 题目 15Using temporary Using filesort 在 JOIN 中怎么理解
  • 题目 16NATURAL JOIN 和 USING 是什么
  • JOIN 速查表
  • 常见坑与注意事项
  • 复习清单
  • 练习题

1 JOIN 高频考点地图

| 难度 | 典型问题 | 本篇对应 |

|---|---|---|

| 入门 | JOIN 类型?LEFT vs INNER? | 题目 1、2 |

| 基础 | NLJ 原理?驱动表? | 题目 3、4 |

| 进阶 | BNL?ON vs WHERE?半连接? | 题目 6、9、12 |

| 实战 | 慢 JOIN 优化?索引? | 题目 7、14 |

一条主线记住:MySQL JOIN 是嵌套循环——小表驱动大表,被驱动表 JOIN 字段要有索引。慢 JOIN 90% 是没索引或驱动表选错。

题目 1:MySQL 支持哪些 JOIN 类型?

考点

  • INNER/LEFT/RIGHT/CROSS;
  • MySQL 不直接支持 FULL OUTER JOIN;
  • NATURAL/USING。

参考答案

| JOIN 类型 | 含义 |

|---|---|

| INNER JOIN | 两表都匹配的行 |

| LEFT JOIN | 左表全保留,右表不匹配补 NULL |

| RIGHT JOIN | 右表全保留,左表不匹配补 NULL |

| CROSS JOIN | 笛卡尔积 |

| STRAIGHT_JOIN | 强制左表驱动 |

| NATURAL JOIN / USING | 同名列自动连接 |

  • MySQL 不直接支持 FULL OUTER JOIN,要用 LEFT JOIN UNION RIGHT JOIN 拼;
  • 生产用得最多:INNER JOIN、LEFT JOIN。
-- 各类 JOIN 示例(MySQL 8.0)
SELECT * FROM orders o INNER JOIN users u ON o.user_id = u.id;
SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id;
SELECT * FROM orders o RIGHT JOIN users u ON o.user_id = u.id;

追问

  • 问:JOIN 逗号写法和 INNER JOIN 区别?答:FROM a, b WHERE a.id=b.id 等价 INNER JOIN,但可读性差,推荐显式 JOIN。
  • 问:FULL OUTER JOIN 怎么写?答:(SELECT ... LEFT JOIN ...) UNION (SELECT ... RIGHT JOIN ...)。

题目 2:INNER / LEFT / RIGHT / FULL JOIN 的区别?

考点

  • 结果集差异;
  • NULL 补全;
  • 业务语义。

参考答案

假设有 users(1,2)、orders(user_id=1):

| JOIN | 结果 |

|---|---|

| INNER JOIN | 只 user_id=1 的行 |

| LEFT JOIN | 所有 users,user_id=2 的 order 字段补 NULL |

| RIGHT JOIN | 所有 orders(这里就一条) |

| FULL OUTER | 两边全保留(MySQL 要 UNION) |

  • LEFT JOIN:要保留左表全部,即使没匹配;常用于"查所有用户及其订单,没订单也要列出用户";
  • INNER JOIN:只要两边都匹配的,常用于"订单必须有对应用户"。
-- 查所有用户及其订单(没订单也列出用户)
SELECT u.id, u.name, o.order_no
FROM users u LEFT JOIN orders o ON u.id = o.user_id;

追问

  • 问:LEFT JOIN 右表没匹配为什么补 NULL?答:SQL 标准定义,左表行必须全保留,右表列填空。
  • 问:RIGHT JOIN 为什么少见?答:可读性差,写 LEFT JOIN 换个表顺序即可。

题目 3:MySQL 怎么做 JOIN?Nested Loop Join 原理

考点

  • 嵌套循环;
  • 外层驱动、内层被驱动;
  • 三种实现:INLJ/BNL/BNL 改进。

参考答案

  • MySQL 的 JOIN 统一叫 Nested Loop Join(嵌套循环连接):

1. 选一张驱动表(外层);

2. 遍历驱动表每一行;

3. 拿这行的 JOIN 字段值去被驱动表(内层)找匹配;

  • 内层查找方式决定性能:
  • Index Nested Loop Join(INLJ):被驱动表 JOIN 字段有索引,用索引找,O(log n);
  • Block Nested Loop Join(BNL):被驱动表没索引,把驱动表一批数据放 join buffer,全扫被驱动表匹配;
  • 没有 Hash Join(MySQL 8.0.18+ 才引入,见追问)。
-- NLJ 伪代码
for row in 驱动表:
    for row2 in 被驱动表 where row2.join_col = row.join_col:
        emit(row, row2)

追问

  • 问:8.0 有 Hash Join 吗?答:8.0.18+ 引入 Hash Join,用于无索引的等值 JOIN,把内表建哈希表,外表探测,比 BNL 快;不等值 JOIN 仍用 NLJ。
  • 问:MySQL 有没有 Sort-Merge Join?答:没有,只有 NLJ 家族 + 8.0 Hash Join。

题目 4:什么是驱动表和被驱动表?优化器怎么选?

考点

  • 驱动表 = 外层;
  • 小表驱动大表;
  • 优化器基于代价选。

参考答案

  • 驱动表(driving table):外层循环遍历的表;被驱动表(driven table):内层查找的表;
  • 选择原则:小表驱动大表——驱动表行数少,内层查找次数少;
  • 优化器基于代价(扫描行数、索引可用性)自动选;
  • EXPLAIN 中 id 相同的表,从上到下就是驱动顺序(第一行是驱动表)。
-- 小表驱动大表示例
-- users 1 万行,orders 1 亿行
-- 优化器应该选 users 驱动 orders(orders.user_id 有索引)
EXPLAIN SELECT u.*, o.order_no FROM users u JOIN orders o ON u.id = o.user_id;
-- EXPLAIN 第一行 type=ref,key=idx_user_id,orders 是被驱动表

追问

  • 问:怎么强制驱动顺序?答:STRAIGHT_JOIN 固定左表驱动右表。
  • 问:小表驱动大表一定对吗?答:原则上对,但优化器会结合过滤条件、索引综合选;必要时用 STRAIGHT_JOIN 兜底。

题目 5:Index Nested Loop Join 是什么?

考点

  • 被驱动表 JOIN 字段有索引;
  • 每次内层走索引;
  • 最优 JOIN。

参考答案

  • INLJ:被驱动表的 JOIN 字段有索引,外层每一行拿 JOIN 值去内层索引查找,O(log n);
  • 这是 MySQL JOIN 的理想状态;
  • EXPLAIN 中被驱动表 type=ref/eq_ref,key 显示用到的索引。
-- orders.user_id 有索引 idx_user_id
EXPLAIN SELECT u.*, o.order_no FROM users u JOIN orders o ON u.id = o.user_id;
-- 被驱动表 orders:type=ref, key=idx_user_id
-- 即 INLJ

追问

  • 问:eq_ref 和 ref 区别?答:eq_ref 是主键/唯一索引等值匹配(最多一行),ref 是非唯一索引匹配多行。
  • 问:覆盖索引能更优吗?答:能,被驱动表要的列都在索引里,Extra=Using index,不回表。

题目 6:Block Nested Loop Join(BNL)是什么?

考点

  • 被驱动表无索引;
  • join buffer 缓存驱动表;
  • 全扫被驱动表匹配。

参考答案

  • BNL:被驱动表 JOIN 字段没有索引时,把驱动表的相关列读入 join buffer,然后全表扫描被驱动表,每一行和 buffer 里的驱动表行批量比较;
  • 避免"每行驱动表都扫一次被驱动表"的 IO 放大;
  • 代价:被驱动表被全扫很多次,性能差;
  • 解决:给被驱动表 JOIN 字段加索引,转成 INLJ。
-- BNL 示例:orders.user_id 没索引
-- EXPLAIN 中 Extra=Using join buffer (Block Nested Loop)
SHOW VARIABLES LIKE 'join_buffer_size';   -- 默认 256K

追问

  • 问:8.0 的 Hash Join 取代 BNL 了吗?答:8.0.18+ 等值 JOIN 无索引时用 Hash Join,比 BNL 快;非等值仍 BNL。
  • 问:join_buffer_size 调大有用吗?答:能缓解但治标不治本,根本解是加索引。

题目 7:为什么 JOIN 字段要加索引?

考点

  • 无索引 = BNL/Hash Join 全扫;
  • 有索引 = INLJ;
  • 性能差几个数量级。

参考答案

  • JOIN 字段没索引,被驱动表每次都要全表扫,时间复杂度 O(N×M);
  • 加索引后走 INLJ,时间复杂度 O(N×log M);
  • 这是"慢 JOIN"最常见的根因;
  • 经验:所有 JOIN/ON 字段都要建索引(注意是被驱动表的字段)。
-- 反例:orders.user_id 没索引
SELECT * FROM users u JOIN orders o ON u.id = o.user_id;  -- BNL,慢
-- 修复:
ALTER TABLE orders ADD KEY idx_user_id (user_id);
-- 再 EXPLAIN:type=ref,走索引

追问

  • 问:驱动表也要加索引吗?答:驱动表通常已经 WHERE 过滤过,它的索引服务过滤条件;JOIN 字段索引主要是被驱动表。
  • 问:JOIN 字段类型不匹配会怎样?答:隐式类型转换导致索引失效,照样全扫。

题目 8:LEFT JOIN 为什么有时被优化成 INNER JOIN?

考点

  • 优化器等价转换;
  • WHERE 条件把 NULL 行过滤掉;
  • 业务语义变化。

参考答案

  • 当 WHERE 条件要求右表字段非空(如 o.status=1),LEFT JOIN 出来的右表 NULL 行被过滤,等价于 INNER JOIN;
  • 优化器会自动做这种转换,执行计划里就变 INNER JOIN;
  • 业务上要小心:本来想"保留左表所有行",加了 WHERE 右表条件后语义变了。
-- 这条 LEFT JOIN 实际等价 INNER JOIN
SELECT u.* FROM users u LEFT JOIN orders o ON u.id=o.user_id
WHERE o.status = 1;   -- o.status 为 NULL 的行被过滤
-- 优化器会按 INNER JOIN 执行

追问

  • 问:想保留左表但过滤右表怎么办?答:把条件放 ON 里:LEFT JOIN orders o ON u.id=o.user_id AND o.status=1。
  • 问:优化器这种转换安全吗?答:SQL 语义等价,结果正确,但要理解它发生了。

题目 9:ON 和 WHERE 的区别?LEFT JOIN 后过滤右表为什么变内连接?

考点

  • ON 在 JOIN 时过滤;
  • WHERE 在 JOIN 后过滤;
  • LEFT JOIN 右表过滤时机关键。

参考答案

  • ON:JOIN 时的匹配条件;LEFT JOIN 时,ON 里对右表的条件只决定"哪些右表行参与匹配",不匹配的右表补 NULL,左表行保留;
  • WHERE:JOIN 完成后的整体过滤;LEFT JOIN 时,WHERE 里对右表字段的条件会把"右表 NULL"的左表行也过滤掉,退化成 INNER JOIN;
  • 经验:LEFT JOIN 想"保留左表但过滤右表子集",条件写 ON;想"JOIN 完再整体过滤",写 WHERE。
-- 写法 1:条件在 ON,左表全保留,右表只匹配 status=1
SELECT u.*, o.order_no FROM users u
LEFT JOIN orders o ON u.id=o.user_id AND o.status=1;

-- 写法 2:条件在 WHERE,退化成 INNER JOIN
SELECT u.*, o.order_no FROM users u
LEFT JOIN orders o ON u.id=o.user_id
WHERE o.status=1;

追问

  • 问:对左表的条件放 ON 还是 WHERE?答:无所谓,LEFT JOIN 左表必须全保留,放哪都一样;习惯放 WHERE。
  • 问:对右表的条件呢?答:严格区分——想保留左表就放 ON。

题目 10:多表 JOIN 最多几张表合适?

考点

  • JOIN 数越多优化器越慢;
  • 经验:≤ 3~5 张;
  • 拆分查询 + 应用层组装。

参考答案

  • JOIN 的表越多:

1. 优化器要枚举所有驱动顺序,O(n!) 复杂度;

2. 中间结果集膨胀;

3. 锁和缓存压力大;

  • 经验:单条 SQL JOIN 不超过 3~5 张表;
  • 超过就拆成多条小查询,在应用层组装(Java/Go 内存 join)。
-- 反例:一次 JOIN 8 张表
SELECT * FROM a
JOIN b ON ... JOIN c ON ... JOIN d ON ...
JOIN e ON ... JOIN f ON ... JOIN g ON ... JOIN h ON ...;
-- 优化器选执行计划慢,执行更慢
-- 正解:拆成 3 条小查询,应用层组装

追问

  • 问:数据仓库星型模型 JOIN 几十张表怎么办?答:那是 OLAP 场景,用 MPP 引擎(ClickHouse/Doris),不是 MySQL。
  • 问:MySQL 有 JOIN 数限制吗?答:optimizer_search_depth 限制枚举深度,默认 63,但实践中别真写这么多。

题目 11:STRAIGHT_JOIN 是什么?什么时候用?

考点

  • 强制左表驱动;
  • 优化器选错时兜底;
  • 谨慎使用。

参考答案

  • STRAIGHT_JOIN:强制左表作为驱动表,右表作为被驱动表,不让优化器改顺序;
  • 使用场景:优化器选错驱动表导致慢查询,EXPLAIN 确认后临时用;
  • 缺点:固化执行计划,数据分布变化后可能反而变慢;
  • 优先解是修正统计信息/索引,STRAIGHT_JOIN 是兜底。
-- 强制 users 驱动 orders
SELECT STRAIGHT_JOIN u.*, o.order_no
FROM users u JOIN orders o ON u.id=o.user_id;

追问

  • 问:STRAIGHT_JOIN 和 JOIN 语法区别?答:STRAIGHT_JOIN 等价 JOIN 但固定驱动顺序。
  • 问:生产能长期用吗?答:尽量别,数据量涨了可能变成新瓶颈;根治是索引和统计信息。

题目 12:Semi Join 半连接是什么?IN 子查询怎么被优化?

考点

  • IN/EXISTS 子查询;
  • 优化器重写成 Semi Join;
  • 8.0 优化。

参考答案

  • Semi Join(半连接):WHERE id IN (SELECT ...) 这类子查询,只要外表行在子查询结果里有匹配就保留,不需要拼出右表完整行;
  • MySQL 优化器会把 IN 子查询自动改写成 Semi Join( materialization / first-match / loosescan 等策略);
  • 8.0 对子查询优化很强,IN (SELECT ...) 通常会被改写成 JOIN,性能不差;
  • 老版本要手工改 JOIN,现在一般不用。
-- 子查询写法
SELECT * FROM users u
WHERE u.id IN (SELECT user_id FROM orders WHERE status=1);
-- 优化器自动改写成 Semi Join,EXPLAIN 看到 Semi join

追问

  • 问:IN 和 EXISTS 哪个快?答:MySQL 优化器现在基本等价,不用纠结;以前说"外表小用 IN,内表小用 EXISTS"的经验在 8.0 弱化。
  • 问:相关子查询为什么慢?答:外层每行都执行一次内层,没被 Semi Join 优化时尤其慢。

题目 13:什么是笛卡尔积?怎么避免?

考点

  • 没写 JOIN 条件;
  • 行数 = N×M;
  • 事故常见。

参考答案

  • 笛卡尔积(Cartesian Product):JOIN 没写 ON 条件,或者条件恒真,结果行数 = 左表行数 × 右表行数;
  • 两张千万级表笛卡尔积就是万亿行,直接拖垮库;
  • 避免:

1. 永远写 ON 条件;

2. EXPLAIN 看到 type=ALL 且 rows 乘积巨大要警惕;

3. code review 检查多表 JOIN。

-- 事故:漏写 ON
SELECT * FROM users, orders;   -- 笛卡尔积,千万别跑
-- 正确:
SELECT * FROM users u JOIN orders o ON u.id=o.user_id;

追问

  • 问:CROSS JOIN 就是笛卡尔积吗?答:是,显式 CROSS JOIN 也是笛卡尔积,慎用。
  • 问:EXPLAIN 怎么识别笛卡尔积?答:被驱动表 type=ALL,rows 是全表行数,且没 key。

题目 14:JOIN 慢怎么排查和优化?

考点

  • EXPLAIN;
  • 被驱动表索引;
  • 小表驱动;
  • 减少 JOIN 数。

参考答案

排查步骤:

1. EXPLAIN:看每个表的 type、key、rows、Extra;

2. 找 type=ALL 的表(全表扫),给 JOIN/WHERE 字段加索引;

3. 确认驱动表是小表,必要时 STRAIGHT_JOIN;

4. 看 Extra:Using join buffer 说明没用上索引;Using temporary/filesort 说明有临时表/排序;

5. 减少 JOIN 表数,拆查询;

6. 只 SELECT 需要的列,避免 SELECT *。

-- 优化前
EXPLAIN SELECT * FROM users u JOIN orders o ON u.id=o.user_id;
-- 优化后
ALTER TABLE orders ADD KEY idx_user_id(user_id);
SELECT u.id, u.name, o.order_no
FROM users u JOIN orders o ON u.id=o.user_id
WHERE u.status = 1;

追问

  • 问:JOIN 了 1 亿行大表怎么办?答:先 WHERE 过滤大表到小结果集再 JOIN;或分库分表;或走 OLAP。
  • 问:索引加了还是慢?答:看是不是回表多,改覆盖索引;看是不是数据量真的大,考虑预聚合。

题目 15:Using temporary / Using filesort 在 JOIN 中怎么理解?

考点

  • 临时表;
  • 额外排序;
  • 优化方向。

参考答案

  • Using temporary:JOIN/GROUP BY/DISTINCT 时需要临时表存中间结果,额外内存/磁盘开销;
  • Using filesort:ORDER BY 没用到索引,需要额外排序(不一定真的用磁盘,是内存排序);
  • 优化:

1. GROUP BY/ORDER BY 的列设计联合索引;

2. 减少 JOIN 后排序的数据量;

3. tmp_table_size、max_heap_table_size 调大,避免临时表落盘。

-- GROUP BY 没索引会 Using temporary
EXPLAIN SELECT user_id, COUNT(*) FROM orders GROUP BY user_id;
-- 加索引 idx_user_id 后临时表消失

追问

  • 问:filesort 一定慢吗?答:小结果集内存 sort 很快;大结果集落盘 sort 慢。
  • 问:临时表落盘怎么看?答:SHOW STATUS LIKE 'Created_tmp_disk_tables',持续增长要优化。

题目 16:NATURAL JOIN 和 USING 是什么?

考点

  • 同名列自动连接;
  • 语法糖;
  • 可读性。

参考答案

  • NATURAL JOIN:自动按同名列连接,不用写 ON;
  • USING(col):指定同名列连接;
  • 都是语法糖,等价显式 ON;
  • 生产少用,因为表结构变化时同名列变化会悄悄改变 JOIN 行为。
-- 等价写法
SELECT * FROM users u JOIN orders o ON u.id = o.user_id;   -- 列名不同
SELECT * FROM users u JOIN orders o USING(id);             -- 列名都是 id
SELECT * FROM users NATURAL JOIN orders;                   -- 所有同名列

追问

  • 问:NATURAL JOIN 有什么风险?答:两表都有 created_at 列会自动拿它当连接条件,结果错乱,不推荐。
  • 问:USING 和 ON 结果完全一样吗?答:USING 会合并同名列,只输出一列。

JOIN 速查表

| 概念 | 一句话 |

|---|---|

| INNER JOIN | 两表都匹配 |

| LEFT JOIN | 左表全保留,右表补 NULL |

| 驱动表 | 外层循环,要小 |

| 被驱动表 | 内层查找,JOIN 字段要有索引 |

| INLJ | 被驱动表有索引,type=ref |

| BNL | 被驱动表无索引,join buffer 全扫 |

| Hash Join | 8.0.18+,等值无索引更快 |

| ON vs WHERE | LEFT JOIN 右表条件放 ON |

| Semi Join | IN 子查询优化成 JOIN |

| 笛卡尔积 | 漏写 ON,行数爆炸 |

| EXPLAIN 信号 | 含义 |

|---|---|

| type=ALL | 全表扫,要加索引 |

| type=ref/eq_ref | 走索引,正常 |

| Using join buffer | 没走索引,BNL |

| Using temporary | 需要临时表 |

| Using filesort | 额外排序 |

常见坑与注意事项

1. JOIN 字段没索引:慢查询第一根因。

2. LEFT JOIN 后 WHERE 右表字段非空:语义悄悄变成 INNER JOIN。

3. JOIN 字段类型不匹配:隐式转换索引失效。

4. 多表 JOIN 超过 5 张:优化器枚举爆炸,拆查询。

5. SELECT *:回表多,JOIN 后取列冗余。

6. 漏写 ON 条件:笛卡尔积,线上事故。

7. STRAIGHT_JOIN 长期用:数据分布变了反而慢,根治靠索引。

8. 以为 EXISTS 一定比 IN 快:8.0 优化器基本等价。

复习清单

| 题目 | 核心结论 | 易错点 |

|---|---|---|

| JOIN 类型 | INNER/LEFT/RIGHT | MySQL 无 FULL OUTER |

| NLJ | 嵌套循环,小表驱动大表 | 忘了 Hash Join |

| 驱动表 | 小表驱动 | 优化器选错要 STRAIGHT_JOIN |

| INLJ | 被驱动表有索引 | 驱动表不用加 JOIN 索引 |

| BNL | 无索引全扫 | 调 join_buffer 治标不治本 |

| ON vs WHERE | LEFT JOIN 右表条件放 ON | WHERE 退化成 INNER |

| 多表 JOIN | ≤3~5 张 | OLAP 才用多表 |

| Semi Join | IN 子查询自动优化 | 手工改写过时 |

| 笛卡尔积 | 漏 ON | type=ALL 警惕 |

| 慢 JOIN | EXPLAIN 找全表扫 | 加索引是根本 |

练习题

练 1:一条 SQL SELECT * FROM users u LEFT JOIN orders o ON u.id=o.user_id WHERE o.status=1,业务想"列出所有用户及其已支付订单,没订单的用户也要显示"。SQL 哪里不对?怎么改?

<details>

<summary>参考答案</summary>

WHERE o.status=1 会把"没订单(o 字段 NULL)"的用户过滤掉,LEFT JOIN 退化成 INNER JOIN。改法:把右表条件放 ON:

SELECT u.*, o.order_no, o.status
FROM users u
LEFT JOIN orders o ON u.id=o.user_id AND o.status=1;

这样没订单的用户也会列出,订单字段为 NULL。

</details>

练 2:EXPLAIN 看到 orders 表 type=ALL, rows=1000000, Extra=Using join buffer。这是什么问题?怎么优化?

<details>

<summary>参考答案</summary>

orders 是被驱动表但 JOIN 字段没索引,走 BNL 全表扫。优化:给 JOIN 字段加索引:

ALTER TABLE orders ADD KEY idx_user_id(user_id);

加完再 EXPLAIN,type 应该变成 ref,Extra 里 Using join buffer 消失。

</details>

练 3:用户表 1 万行,订单表 1 亿行。SELECT * FROM users u JOIN orders o ON u.id=o.user_id WHERE u.city='北京'。优化器应该选谁驱动谁?

<details>

<summary>参考答案</summary>

WHERE u.city='北京' 过滤后 users 可能就几百行(小结果集),应该用 users 驱动 orders;orders.user_id 有索引,走 INLJ。EXPLAIN 第一行是 users,type=ref(走 idx_city),第二行 orders type=ref(走 idx_user_id)。

</details>

下一篇预告:第 10 章 子查询与高级SQL面试精讲

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 9 章 JOIN与多表查询面试精讲》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。