② 多表关联的 SQL 语句到底是如何执行的

对应第 91 ~ 93 讲(+ 第 107 讲 join buffer)· 驱动表 · 嵌套循环 · 内外连接

多表关联的基本概念 嵌套循环关联 Nested-Loop Join —— 最基础的关联执行原理 连接语法:内连接 vs 外连接 多表关联为什么慢?两大根源 性能关键 & EXPLAIN 印证 拆解 SQL 无连接条件 概念落地为执行流程 没索引就踩坑 在 EXPLAIN 中如何识别 循环 · 每条都查一次被驱动表 逐行关联成结果集 三表关联继续放大 select * from t1,t2 where t1.x1=xxx and t1.x2=t2.x2 and t2.x3=xxx t1.x1=xxx → t1 的筛选条件(先筛一波数据) t2.x3=xxx → t2 的筛选条件 t1.x2=t2.x2 → 真正的连接条件(决定 t1 的一行配 t2 的哪几行) 没有连接条件 → 笛卡尔积 select * from t1,t2:t1 的每行 × t2 的每行 10 条 × 5 条 = 50 条 · 结果基本没有意义 驱动表 Driving Table 先按 WHERE 筛选条件查出一波数据 被驱动表 Driven Table 拿驱动表的每行数据去这里查 连接条件:t1.x2 = t2.x2 t1 一行 x2=265 → t2 里 x2=265 的所有行都与它关联(一对多) 第一步 · 查驱动表 按 WHERE 条件筛出一波数据(假设 10 条)· const/ref/index/all 皆有可能 第二步 · 循环查被驱动表 每条数据按 ON 连接条件 + 被驱动表筛选条件去查 · 10 条就查 10 次 第三步 · 关联成结果集 每轮查到的被驱动表数据与当前行逐一关联返回 for (row : 驱动表结果集) { rows2 = 被驱动表 WHERE ON条件 AND 筛选条件; 关联(row, rows2); } // 三表:t1 查 10 条 → t2 查 10 次得 30 条 → t3 再查 30 次 · 层层放大 内连接 INNER JOIN 两表的数据必须完全关联得上,才会返回 连接条件可以放在 WHERE 里(与逗号老写法等价) 外连接 OUTER JOIN LEFT OUTER JOIN:左表关联不上也返回,右表列填 NULL RIGHT OUTER JOIN:右侧表关联不上也返回,左侧列填 NULL 语法限制:外连接的连接条件一般放在 ON 子句里,不放 WHERE 例:员工表 LEFT JOIN 销售业绩表 零业绩的新员工王五也会出现在结果里 · 产品/业绩列 = NULL ① 驱动表筛选走全表扫描 ② 被驱动表循环 N 次 × 全表扫描 被驱动表没索引:嵌套循环里每条数据都要全表扫一遍 → 慢得像蜗牛 解法:两个表都建好索引 · 驱动表查得快 + 被驱动表每轮走索引 → 性能就高 驱动表:WHERE 筛选条件要走索引 避免一开始就全表扫描拖慢整体 被驱动表:连接列必须建索引 这是多表关联性能的重中之重 EXPLAIN 印证 ① · 连接列没索引 被驱动表 type=ALL + Extra=Using join buffer (Block Nested Loop) MySQL 用内存 join buffer 做分块缓存,减少全表扫描次数(补救措施) EXPLAIN 印证 ② · 连接列有索引 被驱动表按主键关联 → type=eq_ref · 按二级索引关联 → type=ref 每轮循环只做一次索引树查找 · 这才是理想状态 图例 执行概念 / 正确做法 连接语法 性能陷阱 补救机制(join buffer) 注解 / EXPLAIN 印证

嵌套循环的执行顺序(务必背下来)

  • • 先查驱动表:按 WHERE 筛选条件拿到一波数据
  • • 对这波数据逐条循环:按 ON 连接条件 + WHERE 筛选条件查被驱动表
  • • 每轮查到的数据与当前行关联,进入结果集
  • • 三表关联:10 → 10 次查询得 30 条 → 再查 30 次,层层放大
  • • 任何一层全表扫描都会被循环次数放大成灾难

内连接 vs 外连接一句话区分

  • • 内连接:两边都关联得上才返回(WHERE 里写连接条件)
  • • 左外连接:左表关联不上也返回,右边补 NULL(ON 子句)
  • • 右外连接:右表关联不上也返回,左边补 NULL
  • • 典型场景:零业绩员工也要出现在报表里
  • • 外连接的连接条件语法上一般放在 ON 子句

多表关联调优先记两条

  • • 驱动表的 WHERE 筛选条件:必须有索引可用
  • • 被驱动表的连接列:必须建索引,否则循环放大全表扫描
  • • 看到 Using join buffer (Block Nested Loop) = 连接列缺索引的信号
  • • 理想形态:被驱动表 type 是 eq_ref 或 ref
  • • 更进一步:能单表查就不要关联(见④ 规则优化页最佳实践)