小表驱动大表——SQL查询优化的核心原则(深度剖析+流程图)

小表驱动大表------SQL查询优化的核心原则(深度剖析+流程图)

本文导航:从 Nested Loop Join 的数学本质出发,结合 13 张流程图,逐层拆解驱动表选择、三维成本模型、索引依赖、Hash Join 新特性,最终给出可落地的优化决策树。适合有一定 SQL 基础、想真正理解优化器行为的后端开发者阅读。


一、引言:一个被反复踩坑的优化原则

在日常 SQL 优化中,你可能遇到过这样的场景:

  • 两张表关联,明明加了索引,查询却依然很慢;
  • EXPLAIN 一看,大表居然排在第一行当了驱动表;
  • IN 改成 EXISTS 后,性能瞬间提升了几十倍。

这些现象背后都指向同一条原则------"小表驱动大表"。然而这条原则被广泛提及的同时,也伴随着大量误解:

常见误解 正确理解
行数少的表就是"小表" 经过 WHERE 过滤后参与 JOIN 的结果集小,才是"小表"
只要小表驱动就一定快 前提是被驱动表关联字段有索引,否则反而可能更慢
MySQL 优化器总能选对驱动表 统计信息过期、数据倾斜时优化器可能选错
Hash Join 出现后这条原则就没用了 Hash Join 同样遵循"小表构建哈希表"的核心思想

本文将从算法原理、数学本质、执行流程、实战优化四个维度,把这条原则讲透。


二、什么是"小表驱动大表"?

2.1 定义

"小表驱动大表"是指在多表关联查询时,**优先使用经过过滤后结果集较小的表作为驱动表(外层表/Outer Table),让数据量较大的表作为被驱动表(内层表/Inner Table)**的优化策略。

2.2 "小表"的真正含义

这里的"小"不是绝对的物理行数,而是经过 WHERE 条件过滤后、真正参与 JOIN 的结果集大小

复制代码
一张 100 万行的表
    │
    ├── WHERE 条件过滤后只剩 100 行 → 它就是"小表"
    └── 没有 WHERE 条件 → 它就是"大表"

关键认知 :优化器在选择驱动表时,看的是过滤后的估算行数(rows),不是表的总行数。这也是为什么同一张表在不同查询中可能时而当驱动表、时而当被驱动表。


三、底层原理:Nested Loop Join 算法深度剖析

3.1 算法执行流程

要理解"小表驱动大表",必须先理解 MySQL 最基础的 JOIN 算法------Nested Loop Join(嵌套循环连接)

它的核心思想是:遍历外层表的每一行,去内层表中查找匹配的行
#mermaid-svg-WP5mSYj1XzmlweHL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WP5mSYj1XzmlweHL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WP5mSYj1XzmlweHL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WP5mSYj1XzmlweHL .error-icon{fill:#552222;}#mermaid-svg-WP5mSYj1XzmlweHL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WP5mSYj1XzmlweHL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WP5mSYj1XzmlweHL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WP5mSYj1XzmlweHL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WP5mSYj1XzmlweHL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WP5mSYj1XzmlweHL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WP5mSYj1XzmlweHL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WP5mSYj1XzmlweHL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WP5mSYj1XzmlweHL .marker.cross{stroke:#333333;}#mermaid-svg-WP5mSYj1XzmlweHL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WP5mSYj1XzmlweHL p{margin:0;}#mermaid-svg-WP5mSYj1XzmlweHL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WP5mSYj1XzmlweHL .cluster-label text{fill:#333;}#mermaid-svg-WP5mSYj1XzmlweHL .cluster-label span{color:#333;}#mermaid-svg-WP5mSYj1XzmlweHL .cluster-label span p{background-color:transparent;}#mermaid-svg-WP5mSYj1XzmlweHL .label text,#mermaid-svg-WP5mSYj1XzmlweHL span{fill:#333;color:#333;}#mermaid-svg-WP5mSYj1XzmlweHL .node rect,#mermaid-svg-WP5mSYj1XzmlweHL .node circle,#mermaid-svg-WP5mSYj1XzmlweHL .node ellipse,#mermaid-svg-WP5mSYj1XzmlweHL .node polygon,#mermaid-svg-WP5mSYj1XzmlweHL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WP5mSYj1XzmlweHL .rough-node .label text,#mermaid-svg-WP5mSYj1XzmlweHL .node .label text,#mermaid-svg-WP5mSYj1XzmlweHL .image-shape .label,#mermaid-svg-WP5mSYj1XzmlweHL .icon-shape .label{text-anchor:middle;}#mermaid-svg-WP5mSYj1XzmlweHL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WP5mSYj1XzmlweHL .rough-node .label,#mermaid-svg-WP5mSYj1XzmlweHL .node .label,#mermaid-svg-WP5mSYj1XzmlweHL .image-shape .label,#mermaid-svg-WP5mSYj1XzmlweHL .icon-shape .label{text-align:center;}#mermaid-svg-WP5mSYj1XzmlweHL .node.clickable{cursor:pointer;}#mermaid-svg-WP5mSYj1XzmlweHL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WP5mSYj1XzmlweHL .arrowheadPath{fill:#333333;}#mermaid-svg-WP5mSYj1XzmlweHL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WP5mSYj1XzmlweHL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WP5mSYj1XzmlweHL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WP5mSYj1XzmlweHL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WP5mSYj1XzmlweHL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WP5mSYj1XzmlweHL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WP5mSYj1XzmlweHL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WP5mSYj1XzmlweHL .cluster text{fill:#333;}#mermaid-svg-WP5mSYj1XzmlweHL .cluster span{color:#333;}#mermaid-svg-WP5mSYj1XzmlweHL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WP5mSYj1XzmlweHL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WP5mSYj1XzmlweHL rect.text{fill:none;stroke-width:0;}#mermaid-svg-WP5mSYj1XzmlweHL .icon-shape,#mermaid-svg-WP5mSYj1XzmlweHL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WP5mSYj1XzmlweHL .icon-shape p,#mermaid-svg-WP5mSYj1XzmlweHL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WP5mSYj1XzmlweHL .icon-shape .label rect,#mermaid-svg-WP5mSYj1XzmlweHL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WP5mSYj1XzmlweHL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WP5mSYj1XzmlweHL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WP5mSYj1XzmlweHL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是



开始 JOIN
读取驱动表第一行
根据关联条件在被驱动表中查找匹配行
找到匹配行?
将匹配行加入结果集
跳过
驱动表还有下一行?
读取驱动表下一行
返回结果集

对应的伪代码:

java 复制代码
// Nested Loop Join 伪代码
for (Row outerRow : driverTable) {           // 外层循环:遍历驱动表
    for (Row innerRow : drivenTable) {        // 内层循环:遍历被驱动表
        if (match(outerRow, innerRow)) {      // 匹配条件判断
            result.add(outerRow, innerRow);   // 返回匹配结果
        }
    }
}

3.2 数学本质:复杂度分析

Nested Loop Join 的时间复杂度取决于内层循环的查找方式:

算法变体 内层查找方式 时间复杂度 说明
Simple NLJ 全表扫描 O(N × M) 每次都扫描整张被驱动表
Index NLJ 索引查找 O(N × logM) 通过 B+ 树快速定位,logM 为树高
Block NLJ join_buffer 批量缓存 O(N × M / batch_size) 减少被驱动表扫描次数

其中 N = 驱动表行数,M = 被驱动表行数

核心洞察 :在 Index NLJ 下,复杂度是 O(N × logM) 。注意 N 和 M 的位置是不对称的------N 是线性因子,M 只出现在对数中。这意味着减少 N(驱动表行数)对性能的影响,远大于减少 M(被驱动表行数)。这就是"小表驱动大表"的数学根源。

3.3 成本模型拆解

一次 JOIN 的总成本可以拆解为三个部分:

复制代码
总成本 = 驱动表扫描成本 + 内层查找次数 × 单次查找成本 + 结果集返回成本
成本维度 小表驱动大表(N=100, M=100万) 大表驱动小表(N=100万, M=100)
驱动表扫描成本 扫描 100 行(极低) 扫描 100 万行(高)
内层查找次数 100 次 100 万次
单次查找成本(有索引) log₂(100万) ≈ 20 次比较 log₂(100) ≈ 7 次比较
总比较次数 100 × 20 = 2,000 100万 × 7 = 7,000,000
系统调用/IO 次数 100 次索引查找 100 万次索引查找

结论一目了然 :虽然单次查找大表比查找小表稍贵(20 vs 7 次比较),但查找次数的差异(100 vs 100万)是决定性的。小表驱动时总比较次数是大表驱动的 1/3500

3.4 三维成本模型:精确拆解 JOIN 总成本

上面的成本模型可以进一步精确化为三维成本模型。一次 JOIN 的总成本由三个独立维度构成,每个维度受驱动表选择的影响截然不同------这是理解"小表驱动大表"为什么有效的最精确框架。
#mermaid-svg-0WmLi8tUlq12vdsB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0WmLi8tUlq12vdsB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0WmLi8tUlq12vdsB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0WmLi8tUlq12vdsB .error-icon{fill:#552222;}#mermaid-svg-0WmLi8tUlq12vdsB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0WmLi8tUlq12vdsB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0WmLi8tUlq12vdsB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0WmLi8tUlq12vdsB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0WmLi8tUlq12vdsB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0WmLi8tUlq12vdsB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0WmLi8tUlq12vdsB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0WmLi8tUlq12vdsB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0WmLi8tUlq12vdsB .marker.cross{stroke:#333333;}#mermaid-svg-0WmLi8tUlq12vdsB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0WmLi8tUlq12vdsB p{margin:0;}#mermaid-svg-0WmLi8tUlq12vdsB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0WmLi8tUlq12vdsB .cluster-label text{fill:#333;}#mermaid-svg-0WmLi8tUlq12vdsB .cluster-label span{color:#333;}#mermaid-svg-0WmLi8tUlq12vdsB .cluster-label span p{background-color:transparent;}#mermaid-svg-0WmLi8tUlq12vdsB .label text,#mermaid-svg-0WmLi8tUlq12vdsB span{fill:#333;color:#333;}#mermaid-svg-0WmLi8tUlq12vdsB .node rect,#mermaid-svg-0WmLi8tUlq12vdsB .node circle,#mermaid-svg-0WmLi8tUlq12vdsB .node ellipse,#mermaid-svg-0WmLi8tUlq12vdsB .node polygon,#mermaid-svg-0WmLi8tUlq12vdsB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0WmLi8tUlq12vdsB .rough-node .label text,#mermaid-svg-0WmLi8tUlq12vdsB .node .label text,#mermaid-svg-0WmLi8tUlq12vdsB .image-shape .label,#mermaid-svg-0WmLi8tUlq12vdsB .icon-shape .label{text-anchor:middle;}#mermaid-svg-0WmLi8tUlq12vdsB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0WmLi8tUlq12vdsB .rough-node .label,#mermaid-svg-0WmLi8tUlq12vdsB .node .label,#mermaid-svg-0WmLi8tUlq12vdsB .image-shape .label,#mermaid-svg-0WmLi8tUlq12vdsB .icon-shape .label{text-align:center;}#mermaid-svg-0WmLi8tUlq12vdsB .node.clickable{cursor:pointer;}#mermaid-svg-0WmLi8tUlq12vdsB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0WmLi8tUlq12vdsB .arrowheadPath{fill:#333333;}#mermaid-svg-0WmLi8tUlq12vdsB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0WmLi8tUlq12vdsB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0WmLi8tUlq12vdsB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0WmLi8tUlq12vdsB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0WmLi8tUlq12vdsB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0WmLi8tUlq12vdsB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0WmLi8tUlq12vdsB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0WmLi8tUlq12vdsB .cluster text{fill:#333;}#mermaid-svg-0WmLi8tUlq12vdsB .cluster span{color:#333;}#mermaid-svg-0WmLi8tUlq12vdsB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0WmLi8tUlq12vdsB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0WmLi8tUlq12vdsB rect.text{fill:none;stroke-width:0;}#mermaid-svg-0WmLi8tUlq12vdsB .icon-shape,#mermaid-svg-0WmLi8tUlq12vdsB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0WmLi8tUlq12vdsB .icon-shape p,#mermaid-svg-0WmLi8tUlq12vdsB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0WmLi8tUlq12vdsB .icon-shape .label rect,#mermaid-svg-0WmLi8tUlq12vdsB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0WmLi8tUlq12vdsB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0WmLi8tUlq12vdsB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0WmLi8tUlq12vdsB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} JOIN 总成本
① 扫描成本
② 表关联成本
③ 数据判断成本
= 驱动表行数 × 行宽
小表驱动: 显著降低 ✅
= 驱动表行数 = 查询发起次数
小表驱动: 显著降低 ✅
= 驱动表行数 × 被驱动表行数
N×M = M×N, 与驱动无关 ❌

维度 阶段成本名称 计算公式 驱动方式影响
扫描成本 驱动表的数据量(行数 × 行宽) ✅ 小表驱动显著更低
表关联成本 驱动表的行数(发起查询的次数) ✅ 小表驱动显著更低
数据判断成本 驱动表行数 × 被驱动表行数(笛卡尔积) ❌ 数学上完全相同,与谁驱动无关

核心结论 :三个维度中,前两个维度都因小表驱动而显著降低,只有第三个维度与驱动方式无关。这就是"小表驱动大表"能带来性能提升的精确数学解释------省的是扫描和关联,不是判断

用数据验证(小表 100 行,大表 100 万行)
阶段 小表驱动大表 大表驱动小表 差异
① 扫描成本 读 100 行 读 100 万行 差 1 万倍
② 表关联成本 发起 100 次查询 发起 100 万次查询 差 1 万倍
③ 数据判断成本 100 × 100万 = 1亿次 100万 × 100 = 1亿次 完全相同
三个阶段的"成本单价"解释
阶段 成本的"单价"是什么 为什么小表驱动省
① 扫描成本 磁盘 IO(读一行/一页的耗时) 驱动表越小,要读的数据越少
② 表关联成本 系统调用 / 网络往返 / 索引查找的固定开销 驱动表越小,发起查询的次数越少
③ 数据判断成本 CPU 比较(一次匹配的耗时) 数学上完全一样,谁也省不了
形象比喻:查字典

假设你要查 100 个生僻字(小表)100 万字的大字典(大表) 中的解释:

阶段 对应操作 小表驱动(正确) 大表驱动(错误)
① 扫描成本 准备要查的字 抄 100 个字 抄 100 万字(累死)
② 表关联成本 跑去查几次 跑 100 趟去查字典 跑 100 万趟去问字典(跑断腿)
③ 数据判断成本 翻页对比确认 最终确认 100 个解释 最终确认 100 个解释(一样)

四、两个场景的执行流程对比

我们用一个具体例子来直观感受差异。假设有:

  • users 表:100 行(小表)
  • orders 表:100 万行(大表)
  • 关联字段user_id,在 orders 表上有 B+ 树索引

4.1 场景 A:小表驱动大表(推荐)

#mermaid-svg-Z6QRs4DmiPpZGyLz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Z6QRs4DmiPpZGyLz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Z6QRs4DmiPpZGyLz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Z6QRs4DmiPpZGyLz .error-icon{fill:#552222;}#mermaid-svg-Z6QRs4DmiPpZGyLz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Z6QRs4DmiPpZGyLz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Z6QRs4DmiPpZGyLz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Z6QRs4DmiPpZGyLz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Z6QRs4DmiPpZGyLz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Z6QRs4DmiPpZGyLz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Z6QRs4DmiPpZGyLz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Z6QRs4DmiPpZGyLz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Z6QRs4DmiPpZGyLz .marker.cross{stroke:#333333;}#mermaid-svg-Z6QRs4DmiPpZGyLz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Z6QRs4DmiPpZGyLz p{margin:0;}#mermaid-svg-Z6QRs4DmiPpZGyLz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Z6QRs4DmiPpZGyLz .cluster-label text{fill:#333;}#mermaid-svg-Z6QRs4DmiPpZGyLz .cluster-label span{color:#333;}#mermaid-svg-Z6QRs4DmiPpZGyLz .cluster-label span p{background-color:transparent;}#mermaid-svg-Z6QRs4DmiPpZGyLz .label text,#mermaid-svg-Z6QRs4DmiPpZGyLz span{fill:#333;color:#333;}#mermaid-svg-Z6QRs4DmiPpZGyLz .node rect,#mermaid-svg-Z6QRs4DmiPpZGyLz .node circle,#mermaid-svg-Z6QRs4DmiPpZGyLz .node ellipse,#mermaid-svg-Z6QRs4DmiPpZGyLz .node polygon,#mermaid-svg-Z6QRs4DmiPpZGyLz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Z6QRs4DmiPpZGyLz .rough-node .label text,#mermaid-svg-Z6QRs4DmiPpZGyLz .node .label text,#mermaid-svg-Z6QRs4DmiPpZGyLz .image-shape .label,#mermaid-svg-Z6QRs4DmiPpZGyLz .icon-shape .label{text-anchor:middle;}#mermaid-svg-Z6QRs4DmiPpZGyLz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Z6QRs4DmiPpZGyLz .rough-node .label,#mermaid-svg-Z6QRs4DmiPpZGyLz .node .label,#mermaid-svg-Z6QRs4DmiPpZGyLz .image-shape .label,#mermaid-svg-Z6QRs4DmiPpZGyLz .icon-shape .label{text-align:center;}#mermaid-svg-Z6QRs4DmiPpZGyLz .node.clickable{cursor:pointer;}#mermaid-svg-Z6QRs4DmiPpZGyLz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Z6QRs4DmiPpZGyLz .arrowheadPath{fill:#333333;}#mermaid-svg-Z6QRs4DmiPpZGyLz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Z6QRs4DmiPpZGyLz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Z6QRs4DmiPpZGyLz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Z6QRs4DmiPpZGyLz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Z6QRs4DmiPpZGyLz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Z6QRs4DmiPpZGyLz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Z6QRs4DmiPpZGyLz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Z6QRs4DmiPpZGyLz .cluster text{fill:#333;}#mermaid-svg-Z6QRs4DmiPpZGyLz .cluster span{color:#333;}#mermaid-svg-Z6QRs4DmiPpZGyLz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Z6QRs4DmiPpZGyLz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Z6QRs4DmiPpZGyLz rect.text{fill:none;stroke-width:0;}#mermaid-svg-Z6QRs4DmiPpZGyLz .icon-shape,#mermaid-svg-Z6QRs4DmiPpZGyLz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Z6QRs4DmiPpZGyLz .icon-shape p,#mermaid-svg-Z6QRs4DmiPpZGyLz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Z6QRs4DmiPpZGyLz .icon-shape .label rect,#mermaid-svg-Z6QRs4DmiPpZGyLz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Z6QRs4DmiPpZGyLz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Z6QRs4DmiPpZGyLz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Z6QRs4DmiPpZGyLz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 内层循环:索引查找 orders
外层循环:遍历 users(100次)
user_id=1
user_id=2
...
user_id=100
B+树索引定位

user_id=1 的订单
B+树索引定位

user_id=2 的订单
B+树索引定位

user_id=100 的订单
结果集

执行步骤

  1. 全表扫描 users 表,取出 100 行(成本极低)
  2. 对每个 user_id,通过 orders 表的 B+ 树索引快速定位对应订单
  3. 总共发起 100 次索引查找

4.2 场景 B:大表驱动小表(不推荐)

#mermaid-svg-9f0MrKd1fu1cC0ED{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9f0MrKd1fu1cC0ED .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9f0MrKd1fu1cC0ED .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9f0MrKd1fu1cC0ED .error-icon{fill:#552222;}#mermaid-svg-9f0MrKd1fu1cC0ED .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9f0MrKd1fu1cC0ED .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9f0MrKd1fu1cC0ED .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9f0MrKd1fu1cC0ED .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9f0MrKd1fu1cC0ED .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9f0MrKd1fu1cC0ED .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9f0MrKd1fu1cC0ED .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9f0MrKd1fu1cC0ED .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9f0MrKd1fu1cC0ED .marker.cross{stroke:#333333;}#mermaid-svg-9f0MrKd1fu1cC0ED svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9f0MrKd1fu1cC0ED p{margin:0;}#mermaid-svg-9f0MrKd1fu1cC0ED .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9f0MrKd1fu1cC0ED .cluster-label text{fill:#333;}#mermaid-svg-9f0MrKd1fu1cC0ED .cluster-label span{color:#333;}#mermaid-svg-9f0MrKd1fu1cC0ED .cluster-label span p{background-color:transparent;}#mermaid-svg-9f0MrKd1fu1cC0ED .label text,#mermaid-svg-9f0MrKd1fu1cC0ED span{fill:#333;color:#333;}#mermaid-svg-9f0MrKd1fu1cC0ED .node rect,#mermaid-svg-9f0MrKd1fu1cC0ED .node circle,#mermaid-svg-9f0MrKd1fu1cC0ED .node ellipse,#mermaid-svg-9f0MrKd1fu1cC0ED .node polygon,#mermaid-svg-9f0MrKd1fu1cC0ED .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9f0MrKd1fu1cC0ED .rough-node .label text,#mermaid-svg-9f0MrKd1fu1cC0ED .node .label text,#mermaid-svg-9f0MrKd1fu1cC0ED .image-shape .label,#mermaid-svg-9f0MrKd1fu1cC0ED .icon-shape .label{text-anchor:middle;}#mermaid-svg-9f0MrKd1fu1cC0ED .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9f0MrKd1fu1cC0ED .rough-node .label,#mermaid-svg-9f0MrKd1fu1cC0ED .node .label,#mermaid-svg-9f0MrKd1fu1cC0ED .image-shape .label,#mermaid-svg-9f0MrKd1fu1cC0ED .icon-shape .label{text-align:center;}#mermaid-svg-9f0MrKd1fu1cC0ED .node.clickable{cursor:pointer;}#mermaid-svg-9f0MrKd1fu1cC0ED .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9f0MrKd1fu1cC0ED .arrowheadPath{fill:#333333;}#mermaid-svg-9f0MrKd1fu1cC0ED .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9f0MrKd1fu1cC0ED .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9f0MrKd1fu1cC0ED .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9f0MrKd1fu1cC0ED .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9f0MrKd1fu1cC0ED .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9f0MrKd1fu1cC0ED .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9f0MrKd1fu1cC0ED .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9f0MrKd1fu1cC0ED .cluster text{fill:#333;}#mermaid-svg-9f0MrKd1fu1cC0ED .cluster span{color:#333;}#mermaid-svg-9f0MrKd1fu1cC0ED div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9f0MrKd1fu1cC0ED .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9f0MrKd1fu1cC0ED rect.text{fill:none;stroke-width:0;}#mermaid-svg-9f0MrKd1fu1cC0ED .icon-shape,#mermaid-svg-9f0MrKd1fu1cC0ED .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9f0MrKd1fu1cC0ED .icon-shape p,#mermaid-svg-9f0MrKd1fu1cC0ED .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9f0MrKd1fu1cC0ED .icon-shape .label rect,#mermaid-svg-9f0MrKd1fu1cC0ED .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9f0MrKd1fu1cC0ED .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9f0MrKd1fu1cC0ED .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9f0MrKd1fu1cC0ED :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 内层循环:索引查找 users
外层循环:遍历 orders(100万次)
order 1
order 2
...
order 100万
B+树索引定位

对应用户
B+树索引定位

对应用户
B+树索引定位

对应用户
结果集

执行步骤

  1. 全表扫描 orders 表,取出 100 万行(成本高)
  2. 对每个订单的 user_id,去 users 表查找匹配用户
  3. 总共发起 100 万次索引查找

4.3 对比总结

#mermaid-svg-zamXR9LbttKKINt1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zamXR9LbttKKINt1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zamXR9LbttKKINt1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zamXR9LbttKKINt1 .error-icon{fill:#552222;}#mermaid-svg-zamXR9LbttKKINt1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zamXR9LbttKKINt1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zamXR9LbttKKINt1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zamXR9LbttKKINt1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zamXR9LbttKKINt1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zamXR9LbttKKINt1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zamXR9LbttKKINt1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zamXR9LbttKKINt1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zamXR9LbttKKINt1 .marker.cross{stroke:#333333;}#mermaid-svg-zamXR9LbttKKINt1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zamXR9LbttKKINt1 p{margin:0;}#mermaid-svg-zamXR9LbttKKINt1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zamXR9LbttKKINt1 .cluster-label text{fill:#333;}#mermaid-svg-zamXR9LbttKKINt1 .cluster-label span{color:#333;}#mermaid-svg-zamXR9LbttKKINt1 .cluster-label span p{background-color:transparent;}#mermaid-svg-zamXR9LbttKKINt1 .label text,#mermaid-svg-zamXR9LbttKKINt1 span{fill:#333;color:#333;}#mermaid-svg-zamXR9LbttKKINt1 .node rect,#mermaid-svg-zamXR9LbttKKINt1 .node circle,#mermaid-svg-zamXR9LbttKKINt1 .node ellipse,#mermaid-svg-zamXR9LbttKKINt1 .node polygon,#mermaid-svg-zamXR9LbttKKINt1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zamXR9LbttKKINt1 .rough-node .label text,#mermaid-svg-zamXR9LbttKKINt1 .node .label text,#mermaid-svg-zamXR9LbttKKINt1 .image-shape .label,#mermaid-svg-zamXR9LbttKKINt1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-zamXR9LbttKKINt1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zamXR9LbttKKINt1 .rough-node .label,#mermaid-svg-zamXR9LbttKKINt1 .node .label,#mermaid-svg-zamXR9LbttKKINt1 .image-shape .label,#mermaid-svg-zamXR9LbttKKINt1 .icon-shape .label{text-align:center;}#mermaid-svg-zamXR9LbttKKINt1 .node.clickable{cursor:pointer;}#mermaid-svg-zamXR9LbttKKINt1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zamXR9LbttKKINt1 .arrowheadPath{fill:#333333;}#mermaid-svg-zamXR9LbttKKINt1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zamXR9LbttKKINt1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zamXR9LbttKKINt1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zamXR9LbttKKINt1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zamXR9LbttKKINt1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zamXR9LbttKKINt1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zamXR9LbttKKINt1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zamXR9LbttKKINt1 .cluster text{fill:#333;}#mermaid-svg-zamXR9LbttKKINt1 .cluster span{color:#333;}#mermaid-svg-zamXR9LbttKKINt1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zamXR9LbttKKINt1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zamXR9LbttKKINt1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-zamXR9LbttKKINt1 .icon-shape,#mermaid-svg-zamXR9LbttKKINt1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zamXR9LbttKKINt1 .icon-shape p,#mermaid-svg-zamXR9LbttKKINt1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zamXR9LbttKKINt1 .icon-shape .label rect,#mermaid-svg-zamXR9LbttKKINt1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zamXR9LbttKKINt1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zamXR9LbttKKINt1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zamXR9LbttKKINt1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 小表驱动
大表驱动
JOIN 查询
谁当驱动表?
外层循环 100 次
内层索引查找 100 次
总 IO 次数: 约 100 次
执行时间: 毫秒级
外层循环 100万 次
内层索引查找 100万 次
总 IO 次数: 约 100万 次
执行时间: 秒级甚至分钟级


五、索引:"小表驱动大表"的生命线

5.1 为什么被驱动表必须有索引?

"小表驱动大表"能成立的关键前提是:被驱动表(大表)的关联字段上必须有索引。
#mermaid-svg-Xxt5WEx3xYSqRikP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Xxt5WEx3xYSqRikP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Xxt5WEx3xYSqRikP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Xxt5WEx3xYSqRikP .error-icon{fill:#552222;}#mermaid-svg-Xxt5WEx3xYSqRikP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Xxt5WEx3xYSqRikP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Xxt5WEx3xYSqRikP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Xxt5WEx3xYSqRikP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Xxt5WEx3xYSqRikP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Xxt5WEx3xYSqRikP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Xxt5WEx3xYSqRikP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Xxt5WEx3xYSqRikP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Xxt5WEx3xYSqRikP .marker.cross{stroke:#333333;}#mermaid-svg-Xxt5WEx3xYSqRikP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Xxt5WEx3xYSqRikP p{margin:0;}#mermaid-svg-Xxt5WEx3xYSqRikP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Xxt5WEx3xYSqRikP .cluster-label text{fill:#333;}#mermaid-svg-Xxt5WEx3xYSqRikP .cluster-label span{color:#333;}#mermaid-svg-Xxt5WEx3xYSqRikP .cluster-label span p{background-color:transparent;}#mermaid-svg-Xxt5WEx3xYSqRikP .label text,#mermaid-svg-Xxt5WEx3xYSqRikP span{fill:#333;color:#333;}#mermaid-svg-Xxt5WEx3xYSqRikP .node rect,#mermaid-svg-Xxt5WEx3xYSqRikP .node circle,#mermaid-svg-Xxt5WEx3xYSqRikP .node ellipse,#mermaid-svg-Xxt5WEx3xYSqRikP .node polygon,#mermaid-svg-Xxt5WEx3xYSqRikP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Xxt5WEx3xYSqRikP .rough-node .label text,#mermaid-svg-Xxt5WEx3xYSqRikP .node .label text,#mermaid-svg-Xxt5WEx3xYSqRikP .image-shape .label,#mermaid-svg-Xxt5WEx3xYSqRikP .icon-shape .label{text-anchor:middle;}#mermaid-svg-Xxt5WEx3xYSqRikP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Xxt5WEx3xYSqRikP .rough-node .label,#mermaid-svg-Xxt5WEx3xYSqRikP .node .label,#mermaid-svg-Xxt5WEx3xYSqRikP .image-shape .label,#mermaid-svg-Xxt5WEx3xYSqRikP .icon-shape .label{text-align:center;}#mermaid-svg-Xxt5WEx3xYSqRikP .node.clickable{cursor:pointer;}#mermaid-svg-Xxt5WEx3xYSqRikP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Xxt5WEx3xYSqRikP .arrowheadPath{fill:#333333;}#mermaid-svg-Xxt5WEx3xYSqRikP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Xxt5WEx3xYSqRikP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Xxt5WEx3xYSqRikP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Xxt5WEx3xYSqRikP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Xxt5WEx3xYSqRikP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Xxt5WEx3xYSqRikP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Xxt5WEx3xYSqRikP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Xxt5WEx3xYSqRikP .cluster text{fill:#333;}#mermaid-svg-Xxt5WEx3xYSqRikP .cluster span{color:#333;}#mermaid-svg-Xxt5WEx3xYSqRikP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Xxt5WEx3xYSqRikP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Xxt5WEx3xYSqRikP rect.text{fill:none;stroke-width:0;}#mermaid-svg-Xxt5WEx3xYSqRikP .icon-shape,#mermaid-svg-Xxt5WEx3xYSqRikP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Xxt5WEx3xYSqRikP .icon-shape p,#mermaid-svg-Xxt5WEx3xYSqRikP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Xxt5WEx3xYSqRikP .icon-shape .label rect,#mermaid-svg-Xxt5WEx3xYSqRikP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Xxt5WEx3xYSqRikP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Xxt5WEx3xYSqRikP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Xxt5WEx3xYSqRikP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有

被驱动表关联字段有索引?
Index Nested-Loop Join
Simple Nested-Loop Join 或 BNLJ
内层通过 B+ 树定位
复杂度 O(N × logM)
每次查找约 logM 次比较
内层全表扫描
复杂度 O(N × M)
每次查找扫描整张表

5.2 有索引 vs 无索引的性能差异

假设驱动表 100 行,被驱动表 100 万行:

场景 内层查找方式 总操作量 预估时间
被驱动表有索引 B+ 树索引查找 100 × 20 次比较 ≈ 2,000 毫秒级
被驱动表无索引 全表扫描 100 × 100万 = 1亿 次比较 分钟级
被驱动表无索引 + BNLJ join_buffer 批量 100万 × (100/batch) 次扫描 秒级~分钟级

注意 :即使被驱动表没有索引,"小表驱动"仍然比"大表驱动"好------因为小表驱动时大表只被全扫 100 次,大表驱动时小表被全扫 100 万次。但这已经是"矮子里拔将军",正确做法是给被驱动表加上索引

5.3 索引设计的三条黄金法则

#mermaid-svg-MorVkDBmTsSAN6M2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MorVkDBmTsSAN6M2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MorVkDBmTsSAN6M2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MorVkDBmTsSAN6M2 .error-icon{fill:#552222;}#mermaid-svg-MorVkDBmTsSAN6M2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MorVkDBmTsSAN6M2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MorVkDBmTsSAN6M2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MorVkDBmTsSAN6M2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MorVkDBmTsSAN6M2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MorVkDBmTsSAN6M2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MorVkDBmTsSAN6M2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MorVkDBmTsSAN6M2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MorVkDBmTsSAN6M2 .marker.cross{stroke:#333333;}#mermaid-svg-MorVkDBmTsSAN6M2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MorVkDBmTsSAN6M2 p{margin:0;}#mermaid-svg-MorVkDBmTsSAN6M2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MorVkDBmTsSAN6M2 .cluster-label text{fill:#333;}#mermaid-svg-MorVkDBmTsSAN6M2 .cluster-label span{color:#333;}#mermaid-svg-MorVkDBmTsSAN6M2 .cluster-label span p{background-color:transparent;}#mermaid-svg-MorVkDBmTsSAN6M2 .label text,#mermaid-svg-MorVkDBmTsSAN6M2 span{fill:#333;color:#333;}#mermaid-svg-MorVkDBmTsSAN6M2 .node rect,#mermaid-svg-MorVkDBmTsSAN6M2 .node circle,#mermaid-svg-MorVkDBmTsSAN6M2 .node ellipse,#mermaid-svg-MorVkDBmTsSAN6M2 .node polygon,#mermaid-svg-MorVkDBmTsSAN6M2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MorVkDBmTsSAN6M2 .rough-node .label text,#mermaid-svg-MorVkDBmTsSAN6M2 .node .label text,#mermaid-svg-MorVkDBmTsSAN6M2 .image-shape .label,#mermaid-svg-MorVkDBmTsSAN6M2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-MorVkDBmTsSAN6M2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MorVkDBmTsSAN6M2 .rough-node .label,#mermaid-svg-MorVkDBmTsSAN6M2 .node .label,#mermaid-svg-MorVkDBmTsSAN6M2 .image-shape .label,#mermaid-svg-MorVkDBmTsSAN6M2 .icon-shape .label{text-align:center;}#mermaid-svg-MorVkDBmTsSAN6M2 .node.clickable{cursor:pointer;}#mermaid-svg-MorVkDBmTsSAN6M2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MorVkDBmTsSAN6M2 .arrowheadPath{fill:#333333;}#mermaid-svg-MorVkDBmTsSAN6M2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MorVkDBmTsSAN6M2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MorVkDBmTsSAN6M2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MorVkDBmTsSAN6M2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MorVkDBmTsSAN6M2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MorVkDBmTsSAN6M2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MorVkDBmTsSAN6M2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MorVkDBmTsSAN6M2 .cluster text{fill:#333;}#mermaid-svg-MorVkDBmTsSAN6M2 .cluster span{color:#333;}#mermaid-svg-MorVkDBmTsSAN6M2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MorVkDBmTsSAN6M2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MorVkDBmTsSAN6M2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-MorVkDBmTsSAN6M2 .icon-shape,#mermaid-svg-MorVkDBmTsSAN6M2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MorVkDBmTsSAN6M2 .icon-shape p,#mermaid-svg-MorVkDBmTsSAN6M2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MorVkDBmTsSAN6M2 .icon-shape .label rect,#mermaid-svg-MorVkDBmTsSAN6M2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MorVkDBmTsSAN6M2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MorVkDBmTsSAN6M2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MorVkDBmTsSAN6M2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 索引设计三原则
被驱动表关联字段必须建索引
优先使用覆盖索引避免回表
关联字段类型必须一致
CREATE INDEX idx_user_id

ON orders(user_id)
CREATE INDEX idx_user_cover

ON orders(user_id, amount, date)
避免 INT vs VARCHAR

隐式转换导致索引失效

法则一:被驱动表关联字段必须建索引

这是 JOIN 优化的第一原则,没有之一。

sql 复制代码
-- 正确:在被驱动表 orders 的关联字段上建索引
CREATE INDEX idx_user_id ON orders(user_id);

法则二:优先使用覆盖索引

如果查询只需要被驱动表的某些列,可以将这些列加入索引,避免回表查询。

sql 复制代码
-- 覆盖索引:查询需要 user_id, order_amount, order_date
CREATE INDEX idx_user_cover ON orders(user_id, order_amount, order_date);

-- 此时以下查询无需回表
SELECT u.name, o.order_amount, o.order_date
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 1;

MySQL 8.0 提示 :MySQL 不支持 INCLUDE 语法(那是 SQL Server / PostgreSQL 的语法),MySQL 的覆盖索引需要把列直接放在索引列中。

法则三:关联字段类型必须一致

如果 users.idINT,而 orders.user_idVARCHAR,MySQL 会进行隐式类型转换,导致索引失效。

sql 复制代码
-- 错误示范:类型不一致,索引失效
-- users.id INT, orders.user_id VARCHAR
SELECT * FROM users u JOIN orders o ON u.id = o.user_id;

-- 正确做法:统一字段类型
ALTER TABLE orders MODIFY COLUMN user_id INT NOT NULL;

六、Hash Join:MySQL 8.0 的 JOIN 革命

6.1 从 BNLJ 到 Hash Join 的演进

在 MySQL 8.0.18 之前,如果两张表的关联字段都没有可用索引,MySQL 只能使用 Block Nested-Loop Join(BNLJ) :将驱动表分批加载到 join_buffer 中,然后扫描被驱动表,与 buffer 中的数据批量匹配。

MySQL 8.0.18 引入了 Hash Join,这是 JOIN 算法的一次重大升级。

6.2 Hash Join 执行流程

#mermaid-svg-C7NIzjtEzJBnFOFN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-C7NIzjtEzJBnFOFN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-C7NIzjtEzJBnFOFN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-C7NIzjtEzJBnFOFN .error-icon{fill:#552222;}#mermaid-svg-C7NIzjtEzJBnFOFN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-C7NIzjtEzJBnFOFN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-C7NIzjtEzJBnFOFN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-C7NIzjtEzJBnFOFN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-C7NIzjtEzJBnFOFN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-C7NIzjtEzJBnFOFN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-C7NIzjtEzJBnFOFN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-C7NIzjtEzJBnFOFN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-C7NIzjtEzJBnFOFN .marker.cross{stroke:#333333;}#mermaid-svg-C7NIzjtEzJBnFOFN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-C7NIzjtEzJBnFOFN p{margin:0;}#mermaid-svg-C7NIzjtEzJBnFOFN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-C7NIzjtEzJBnFOFN .cluster-label text{fill:#333;}#mermaid-svg-C7NIzjtEzJBnFOFN .cluster-label span{color:#333;}#mermaid-svg-C7NIzjtEzJBnFOFN .cluster-label span p{background-color:transparent;}#mermaid-svg-C7NIzjtEzJBnFOFN .label text,#mermaid-svg-C7NIzjtEzJBnFOFN span{fill:#333;color:#333;}#mermaid-svg-C7NIzjtEzJBnFOFN .node rect,#mermaid-svg-C7NIzjtEzJBnFOFN .node circle,#mermaid-svg-C7NIzjtEzJBnFOFN .node ellipse,#mermaid-svg-C7NIzjtEzJBnFOFN .node polygon,#mermaid-svg-C7NIzjtEzJBnFOFN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-C7NIzjtEzJBnFOFN .rough-node .label text,#mermaid-svg-C7NIzjtEzJBnFOFN .node .label text,#mermaid-svg-C7NIzjtEzJBnFOFN .image-shape .label,#mermaid-svg-C7NIzjtEzJBnFOFN .icon-shape .label{text-anchor:middle;}#mermaid-svg-C7NIzjtEzJBnFOFN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-C7NIzjtEzJBnFOFN .rough-node .label,#mermaid-svg-C7NIzjtEzJBnFOFN .node .label,#mermaid-svg-C7NIzjtEzJBnFOFN .image-shape .label,#mermaid-svg-C7NIzjtEzJBnFOFN .icon-shape .label{text-align:center;}#mermaid-svg-C7NIzjtEzJBnFOFN .node.clickable{cursor:pointer;}#mermaid-svg-C7NIzjtEzJBnFOFN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-C7NIzjtEzJBnFOFN .arrowheadPath{fill:#333333;}#mermaid-svg-C7NIzjtEzJBnFOFN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-C7NIzjtEzJBnFOFN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-C7NIzjtEzJBnFOFN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-C7NIzjtEzJBnFOFN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-C7NIzjtEzJBnFOFN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-C7NIzjtEzJBnFOFN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-C7NIzjtEzJBnFOFN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-C7NIzjtEzJBnFOFN .cluster text{fill:#333;}#mermaid-svg-C7NIzjtEzJBnFOFN .cluster span{color:#333;}#mermaid-svg-C7NIzjtEzJBnFOFN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-C7NIzjtEzJBnFOFN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-C7NIzjtEzJBnFOFN rect.text{fill:none;stroke-width:0;}#mermaid-svg-C7NIzjtEzJBnFOFN .icon-shape,#mermaid-svg-C7NIzjtEzJBnFOFN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-C7NIzjtEzJBnFOFN .icon-shape p,#mermaid-svg-C7NIzjtEzJBnFOFN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-C7NIzjtEzJBnFOFN .icon-shape .label rect,#mermaid-svg-C7NIzjtEzJBnFOFN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-C7NIzjtEzJBnFOFN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-C7NIzjtEzJBnFOFN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-C7NIzjtEzJBnFOFN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是





开始 Hash Join
选择较小的表作为构建表 Build Input
遍历构建表, 对连接列计算哈希值
在内存中构建哈希表 Hash Table
哈希表能放入内存?
内存中 Hash Join
溢出到磁盘: 分块 OnDiskHashJoin
将构建表和探测表都按哈希值分块写入磁盘
逐块加载到内存进行 Hash Join
遍历探测表 Probe Input
对探测行的连接列计算哈希值
在哈希表中查找匹配
找到匹配?
加入结果集
跳过
探测表还有行?
返回结果集

6.3 Hash Join 的两个阶段

Hash Join 分为两个核心阶段:

阶段一:构建阶段(Build Phase)

复制代码
选择较小的表 → 对连接列计算哈希值 → 在内存中构建哈希表
  • 选择较小的表作为构建输入(Build Input)
  • 遍历构建表的每一行,对连接键计算哈希值
  • 将哈希值和行数据存入内存哈希表

阶段二:探测阶段(Probe Phase)

复制代码
遍历较大的表 → 对连接列计算哈希值 → 在哈希表中查找匹配
  • 遍历较大的表(探测输入 Probe Input)
  • 对每行的连接键计算相同的哈希函数
  • 在哈希表中查找相同哈希值的桶,进行精确匹配

6.4 BNLJ vs Hash Join 对比

对比项 Block NLJ(MySQL 5.7) Hash Join(MySQL 8.0.18+)
核心原理 批量缓存 + 全表扫描 哈希表构建 + 内存探测
时间复杂度 O(N × M / batch_size) O(N + M)
空间复杂度 O(batch_size) O(小表大小)
适用场景 小表 + 无索引 大表 + 无索引时碾压 BNLJ
等值 JOIN 支持 支持
非等值 JOIN 支持 不支持(仅等值连接)
内存溢出处理 无特殊处理 自动分块溢出到磁盘

6.5 Hash Join 下"小表驱动大表"还成立吗?

依然成立,而且更加重要。

在 Hash Join 中,"小表"决定了哈希表的内存占用:

  • 小表作为构建表 → 哈希表小 → 内存占用低 → 哈希冲突少 → 探测快
  • 大表作为构建表 → 哈希表大 → 可能溢出到磁盘 → 性能急剧下降

#mermaid-svg-OeIs4Zoh1ZYU6u3n{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .error-icon{fill:#552222;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .marker.cross{stroke:#333333;}#mermaid-svg-OeIs4Zoh1ZYU6u3n svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OeIs4Zoh1ZYU6u3n p{margin:0;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .cluster-label text{fill:#333;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .cluster-label span{color:#333;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .cluster-label span p{background-color:transparent;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .label text,#mermaid-svg-OeIs4Zoh1ZYU6u3n span{fill:#333;color:#333;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .node rect,#mermaid-svg-OeIs4Zoh1ZYU6u3n .node circle,#mermaid-svg-OeIs4Zoh1ZYU6u3n .node ellipse,#mermaid-svg-OeIs4Zoh1ZYU6u3n .node polygon,#mermaid-svg-OeIs4Zoh1ZYU6u3n .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .rough-node .label text,#mermaid-svg-OeIs4Zoh1ZYU6u3n .node .label text,#mermaid-svg-OeIs4Zoh1ZYU6u3n .image-shape .label,#mermaid-svg-OeIs4Zoh1ZYU6u3n .icon-shape .label{text-anchor:middle;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .rough-node .label,#mermaid-svg-OeIs4Zoh1ZYU6u3n .node .label,#mermaid-svg-OeIs4Zoh1ZYU6u3n .image-shape .label,#mermaid-svg-OeIs4Zoh1ZYU6u3n .icon-shape .label{text-align:center;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .node.clickable{cursor:pointer;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .arrowheadPath{fill:#333333;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OeIs4Zoh1ZYU6u3n .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OeIs4Zoh1ZYU6u3n .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OeIs4Zoh1ZYU6u3n .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .cluster text{fill:#333;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .cluster span{color:#333;}#mermaid-svg-OeIs4Zoh1ZYU6u3n div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OeIs4Zoh1ZYU6u3n rect.text{fill:none;stroke-width:0;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .icon-shape,#mermaid-svg-OeIs4Zoh1ZYU6u3n .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .icon-shape p,#mermaid-svg-OeIs4Zoh1ZYU6u3n .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .icon-shape .label rect,#mermaid-svg-OeIs4Zoh1ZYU6u3n .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OeIs4Zoh1ZYU6u3n .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OeIs4Zoh1ZYU6u3n .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OeIs4Zoh1ZYU6u3n :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 表A 小
表B 小
Hash Join 构建表选择
哪张表更小?
表A 构建哈希表
表B 构建哈希表
哈希表小, 内存友好
哈希冲突少, 探测快
性能优异
哈希表大, 可能溢出磁盘
分块读写增加 IO 开销
性能下降

Hash Join 的限制 :只能用于等值 JOIN(=),非等值条件(><BETWEEN)无法使用 Hash Join,会退化为 BNLJ 或其他算法。

6.6 如何确认使用了 Hash Join?

sql 复制代码
EXPLAIN FORMAT=TREE
SELECT * FROM users u JOIN orders o ON u.id = o.user_id;

输出中如果看到 Hash Join 字样,说明使用了 Hash Join:

复制代码
-> Inner hash join (o.user_id = u.id)  (cost=... rows=...)
    -> Table scan on o  (cost=... rows=...)
    -> Hash
        -> Table scan on u  (cost=... rows=...)

七、优化器如何选择驱动表?

7.1 优化器的决策逻辑

MySQL 优化器在选择驱动表时,基于**成本模型(Cost-Based Optimization, CBO)**进行决策:
#mermaid-svg-67m8WfQbYCrA62ke{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-67m8WfQbYCrA62ke .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-67m8WfQbYCrA62ke .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-67m8WfQbYCrA62ke .error-icon{fill:#552222;}#mermaid-svg-67m8WfQbYCrA62ke .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-67m8WfQbYCrA62ke .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-67m8WfQbYCrA62ke .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-67m8WfQbYCrA62ke .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-67m8WfQbYCrA62ke .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-67m8WfQbYCrA62ke .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-67m8WfQbYCrA62ke .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-67m8WfQbYCrA62ke .marker{fill:#333333;stroke:#333333;}#mermaid-svg-67m8WfQbYCrA62ke .marker.cross{stroke:#333333;}#mermaid-svg-67m8WfQbYCrA62ke svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-67m8WfQbYCrA62ke p{margin:0;}#mermaid-svg-67m8WfQbYCrA62ke .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-67m8WfQbYCrA62ke .cluster-label text{fill:#333;}#mermaid-svg-67m8WfQbYCrA62ke .cluster-label span{color:#333;}#mermaid-svg-67m8WfQbYCrA62ke .cluster-label span p{background-color:transparent;}#mermaid-svg-67m8WfQbYCrA62ke .label text,#mermaid-svg-67m8WfQbYCrA62ke span{fill:#333;color:#333;}#mermaid-svg-67m8WfQbYCrA62ke .node rect,#mermaid-svg-67m8WfQbYCrA62ke .node circle,#mermaid-svg-67m8WfQbYCrA62ke .node ellipse,#mermaid-svg-67m8WfQbYCrA62ke .node polygon,#mermaid-svg-67m8WfQbYCrA62ke .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-67m8WfQbYCrA62ke .rough-node .label text,#mermaid-svg-67m8WfQbYCrA62ke .node .label text,#mermaid-svg-67m8WfQbYCrA62ke .image-shape .label,#mermaid-svg-67m8WfQbYCrA62ke .icon-shape .label{text-anchor:middle;}#mermaid-svg-67m8WfQbYCrA62ke .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-67m8WfQbYCrA62ke .rough-node .label,#mermaid-svg-67m8WfQbYCrA62ke .node .label,#mermaid-svg-67m8WfQbYCrA62ke .image-shape .label,#mermaid-svg-67m8WfQbYCrA62ke .icon-shape .label{text-align:center;}#mermaid-svg-67m8WfQbYCrA62ke .node.clickable{cursor:pointer;}#mermaid-svg-67m8WfQbYCrA62ke .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-67m8WfQbYCrA62ke .arrowheadPath{fill:#333333;}#mermaid-svg-67m8WfQbYCrA62ke .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-67m8WfQbYCrA62ke .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-67m8WfQbYCrA62ke .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-67m8WfQbYCrA62ke .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-67m8WfQbYCrA62ke .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-67m8WfQbYCrA62ke .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-67m8WfQbYCrA62ke .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-67m8WfQbYCrA62ke .cluster text{fill:#333;}#mermaid-svg-67m8WfQbYCrA62ke .cluster span{color:#333;}#mermaid-svg-67m8WfQbYCrA62ke div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-67m8WfQbYCrA62ke .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-67m8WfQbYCrA62ke rect.text{fill:none;stroke-width:0;}#mermaid-svg-67m8WfQbYCrA62ke .icon-shape,#mermaid-svg-67m8WfQbYCrA62ke .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-67m8WfQbYCrA62ke .icon-shape p,#mermaid-svg-67m8WfQbYCrA62ke .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-67m8WfQbYCrA62ke .icon-shape .label rect,#mermaid-svg-67m8WfQbYCrA62ke .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-67m8WfQbYCrA62ke .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-67m8WfQbYCrA62ke .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-67m8WfQbYCrA62ke :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 小表驱动成本低
大表驱动成本低
无法确定
收到 JOIN 查询
统计信息分析
估算每张表过滤后的行数
计算不同 JOIN 顺序的成本
哪种顺序成本最低?
选择小表作为驱动表
选择大表作为驱动表
使用默认顺序或启发式规则
生成执行计划

7.2 优化器可能选错的场景

优化器不是万能的,以下场景可能导致选错驱动表:

场景 原因 解决方案
统计信息过期 表数据大量变更后未更新统计信息 ANALYZE TABLE
数据严重倾斜 某些值出现频率远高于平均值,估算偏差大 增加直方图统计
复杂子查询 子查询结果集大小难以准确估算 改写为 JOIN 或使用临时表
索引区分度低 优化器低估或高估索引效率 检查索引选择度
字符串关联 字符集/排序规则不一致导致隐式转换 统一字符集和类型

7.3 更新统计信息

sql 复制代码
-- 更新单表统计信息
ANALYZE TABLE users;
ANALYZE TABLE orders;

-- 查看统计信息是否最新
SHOW INDEX FROM orders;
-- 观察 Cardinality 列是否接近实际数据分布

八、实战优化决策树

8.1 JOIN 优化完整决策流程

#mermaid-svg-gBkysmFvHCINbCNP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gBkysmFvHCINbCNP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gBkysmFvHCINbCNP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gBkysmFvHCINbCNP .error-icon{fill:#552222;}#mermaid-svg-gBkysmFvHCINbCNP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gBkysmFvHCINbCNP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gBkysmFvHCINbCNP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gBkysmFvHCINbCNP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gBkysmFvHCINbCNP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gBkysmFvHCINbCNP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gBkysmFvHCINbCNP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gBkysmFvHCINbCNP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gBkysmFvHCINbCNP .marker.cross{stroke:#333333;}#mermaid-svg-gBkysmFvHCINbCNP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gBkysmFvHCINbCNP p{margin:0;}#mermaid-svg-gBkysmFvHCINbCNP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gBkysmFvHCINbCNP .cluster-label text{fill:#333;}#mermaid-svg-gBkysmFvHCINbCNP .cluster-label span{color:#333;}#mermaid-svg-gBkysmFvHCINbCNP .cluster-label span p{background-color:transparent;}#mermaid-svg-gBkysmFvHCINbCNP .label text,#mermaid-svg-gBkysmFvHCINbCNP span{fill:#333;color:#333;}#mermaid-svg-gBkysmFvHCINbCNP .node rect,#mermaid-svg-gBkysmFvHCINbCNP .node circle,#mermaid-svg-gBkysmFvHCINbCNP .node ellipse,#mermaid-svg-gBkysmFvHCINbCNP .node polygon,#mermaid-svg-gBkysmFvHCINbCNP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gBkysmFvHCINbCNP .rough-node .label text,#mermaid-svg-gBkysmFvHCINbCNP .node .label text,#mermaid-svg-gBkysmFvHCINbCNP .image-shape .label,#mermaid-svg-gBkysmFvHCINbCNP .icon-shape .label{text-anchor:middle;}#mermaid-svg-gBkysmFvHCINbCNP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gBkysmFvHCINbCNP .rough-node .label,#mermaid-svg-gBkysmFvHCINbCNP .node .label,#mermaid-svg-gBkysmFvHCINbCNP .image-shape .label,#mermaid-svg-gBkysmFvHCINbCNP .icon-shape .label{text-align:center;}#mermaid-svg-gBkysmFvHCINbCNP .node.clickable{cursor:pointer;}#mermaid-svg-gBkysmFvHCINbCNP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gBkysmFvHCINbCNP .arrowheadPath{fill:#333333;}#mermaid-svg-gBkysmFvHCINbCNP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gBkysmFvHCINbCNP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gBkysmFvHCINbCNP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gBkysmFvHCINbCNP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gBkysmFvHCINbCNP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gBkysmFvHCINbCNP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gBkysmFvHCINbCNP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gBkysmFvHCINbCNP .cluster text{fill:#333;}#mermaid-svg-gBkysmFvHCINbCNP .cluster span{color:#333;}#mermaid-svg-gBkysmFvHCINbCNP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gBkysmFvHCINbCNP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gBkysmFvHCINbCNP rect.text{fill:none;stroke-width:0;}#mermaid-svg-gBkysmFvHCINbCNP .icon-shape,#mermaid-svg-gBkysmFvHCINbCNP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gBkysmFvHCINbCNP .icon-shape p,#mermaid-svg-gBkysmFvHCINbCNP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gBkysmFvHCINbCNP .icon-shape .label rect,#mermaid-svg-gBkysmFvHCINbCNP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gBkysmFvHCINbCNP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gBkysmFvHCINbCNP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gBkysmFvHCINbCNP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 无

否, 大表在驱动
确认小表应驱动
优化器选择合理

是, MySQL 8.0+



JOIN 查询性能差
EXPLAIN 查看执行计划
被驱动表有关联字段索引?
给被驱动表关联字段加索引
重新 EXPLAIN 验证
驱动表是小表吗?
检查 WHERE 过滤条件
过滤后哪张表更小?
使用 STRAIGHT_JOIN 强制顺序
接受当前计划
是否为等值 JOIN 且无索引?
确认使用 Hash Join
确认 Index NLJ 正常工作
重新 EXPLAIN + 性能测试
性能达标?
优化完成
考虑覆盖索引/查询改写/架构优化

8.2 使用 EXPLAIN 验证驱动表

EXPLAIN 输出中,第一行即为驱动表

sql 复制代码
EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id;
复制代码
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
|  1 | SIMPLE      | u     | ALL  | NULL          | NULL | NULL    | NULL |     100 |             |
|  1 | SIMPLE      | o     | ref  | idx_user_id   | idx_user_id | 5 | db.u.id |    5000 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+

解读要点

字段 含义 本例解读
table(第一行) 驱动表 u(users)是驱动表 ✅
type 访问类型 ALL = 全表扫描(驱动表可以接受)
rows 估算扫描行数 100 行(小表)
key(第二行) 实际使用的索引 idx_user_id(被驱动表走索引 ✅)
ref 索引比较的列 db.u.id(关联到驱动表的 id)

判断标准:第一行 table 是过滤后行数少的表 → 驱动表选择正确;第二行 key 不为 NULL → 被驱动表走索引。两个条件同时满足,JOIN 基本就是最优的。

8.3 强制指定驱动表:STRAIGHT_JOIN

当优化器因统计信息过期等原因选错驱动表时,可以使用 STRAIGHT_JOIN 强制指定 JOIN 顺序。STRAIGHT_JOIN 会强制左表为驱动表。

sql 复制代码
-- 强制 users 为驱动表
SELECT STRAIGHT_JOIN *
FROM users u
JOIN orders o ON u.id = o.user_id;

⚠️ 风险提示

  1. 强制指定可能因数据分布变化导致性能退化,需配合 ANALYZE TABLE 定期更新统计信息;
  2. 优先尝试更新统计信息让优化器自己选对,STRAIGHT_JOIN 是最后手段;
  3. 加了 STRAIGHT_JOIN 后要做性能回归测试。

8.4 EXISTS vs IN:子查询的驱动表选择

在子查询场景中,EXISTSIN 的执行逻辑不同,直接影响驱动表选择。
#mermaid-svg-lyM9LMgnyCa7DUyL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lyM9LMgnyCa7DUyL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lyM9LMgnyCa7DUyL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lyM9LMgnyCa7DUyL .error-icon{fill:#552222;}#mermaid-svg-lyM9LMgnyCa7DUyL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lyM9LMgnyCa7DUyL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lyM9LMgnyCa7DUyL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lyM9LMgnyCa7DUyL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lyM9LMgnyCa7DUyL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lyM9LMgnyCa7DUyL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lyM9LMgnyCa7DUyL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lyM9LMgnyCa7DUyL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lyM9LMgnyCa7DUyL .marker.cross{stroke:#333333;}#mermaid-svg-lyM9LMgnyCa7DUyL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lyM9LMgnyCa7DUyL p{margin:0;}#mermaid-svg-lyM9LMgnyCa7DUyL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lyM9LMgnyCa7DUyL .cluster-label text{fill:#333;}#mermaid-svg-lyM9LMgnyCa7DUyL .cluster-label span{color:#333;}#mermaid-svg-lyM9LMgnyCa7DUyL .cluster-label span p{background-color:transparent;}#mermaid-svg-lyM9LMgnyCa7DUyL .label text,#mermaid-svg-lyM9LMgnyCa7DUyL span{fill:#333;color:#333;}#mermaid-svg-lyM9LMgnyCa7DUyL .node rect,#mermaid-svg-lyM9LMgnyCa7DUyL .node circle,#mermaid-svg-lyM9LMgnyCa7DUyL .node ellipse,#mermaid-svg-lyM9LMgnyCa7DUyL .node polygon,#mermaid-svg-lyM9LMgnyCa7DUyL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lyM9LMgnyCa7DUyL .rough-node .label text,#mermaid-svg-lyM9LMgnyCa7DUyL .node .label text,#mermaid-svg-lyM9LMgnyCa7DUyL .image-shape .label,#mermaid-svg-lyM9LMgnyCa7DUyL .icon-shape .label{text-anchor:middle;}#mermaid-svg-lyM9LMgnyCa7DUyL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lyM9LMgnyCa7DUyL .rough-node .label,#mermaid-svg-lyM9LMgnyCa7DUyL .node .label,#mermaid-svg-lyM9LMgnyCa7DUyL .image-shape .label,#mermaid-svg-lyM9LMgnyCa7DUyL .icon-shape .label{text-align:center;}#mermaid-svg-lyM9LMgnyCa7DUyL .node.clickable{cursor:pointer;}#mermaid-svg-lyM9LMgnyCa7DUyL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lyM9LMgnyCa7DUyL .arrowheadPath{fill:#333333;}#mermaid-svg-lyM9LMgnyCa7DUyL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lyM9LMgnyCa7DUyL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lyM9LMgnyCa7DUyL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lyM9LMgnyCa7DUyL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lyM9LMgnyCa7DUyL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lyM9LMgnyCa7DUyL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lyM9LMgnyCa7DUyL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lyM9LMgnyCa7DUyL .cluster text{fill:#333;}#mermaid-svg-lyM9LMgnyCa7DUyL .cluster span{color:#333;}#mermaid-svg-lyM9LMgnyCa7DUyL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lyM9LMgnyCa7DUyL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lyM9LMgnyCa7DUyL rect.text{fill:none;stroke-width:0;}#mermaid-svg-lyM9LMgnyCa7DUyL .icon-shape,#mermaid-svg-lyM9LMgnyCa7DUyL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lyM9LMgnyCa7DUyL .icon-shape p,#mermaid-svg-lyM9LMgnyCa7DUyL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lyM9LMgnyCa7DUyL .icon-shape .label rect,#mermaid-svg-lyM9LMgnyCa7DUyL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lyM9LMgnyCa7DUyL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lyM9LMgnyCa7DUyL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lyM9LMgnyCa7DUyL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} EXISTS 子查询(小表驱动)
遍历 large_table 每一行
执行 EXISTS 子查询
找到即返回, 短路优化
小表实际参与匹配
IN 子查询(大表驱动)
遍历 large_table 每一行
去子查询结果中查找
large_table 当驱动表

MySQL 8.0 之前

sql 复制代码
-- 不推荐:IN 子查询可能被优化为大表驱动
SELECT * FROM large_table l 
WHERE l.id IN (SELECT id FROM small_table);

-- 推荐:EXISTS 实现小表驱动效果
SELECT * FROM large_table l 
WHERE EXISTS (
    SELECT 1 FROM small_table s WHERE s.id = l.id
);

MySQL 8.0 重要更新 :MySQL 8.0 优化器会自动将 IN 子查询转换为 SEMI JOIN(半连接),并自动选择最优的执行策略。因此在 8.0 中,INEXISTS 的性能差异已经大幅缩小。但在复杂查询或统计信息不准时,EXISTS 仍然是更可控的选择。

8.5 完整优化示例

假设我们有一个查询需要优化:

sql 复制代码
-- 原始查询:慢
SELECT o.id, o.amount, u.name, c.city_name
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN cities c ON u.city_id = c.id
WHERE o.status = 'PAID'
  AND o.create_time >= '2026-01-01';

优化步骤

sql 复制代码
-- 步骤1:EXPLAIN 分析
EXPLAIN SELECT o.id, o.amount, u.name, c.city_name
FROM orders o JOIN users u ON o.user_id = u.id
JOIN cities c ON u.city_id = c.id
WHERE o.status = 'PAID' AND o.create_time >= '2026-01-01';

-- 步骤2:确认过滤后哪张表最小
-- orders 经过 WHERE 过滤后可能只剩 5000 行 → 实际是"小表"
-- users 100 行 → 更小
-- cities 50 行 → 最小

-- 步骤3:确保被驱动表有索引
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_users_city_id ON users(city_id);

-- 步骤4:考虑覆盖索引
CREATE INDEX idx_orders_status_time_user 
ON orders(status, create_time, user_id, amount);

-- 步骤5:优化后查询(让优化器自动选择,通常 cities 会当驱动表)
SELECT o.id, o.amount, u.name, c.city_name
FROM cities c
JOIN users u ON c.id = u.city_id
JOIN orders o ON u.id = o.user_id
WHERE o.status = 'PAID'
  AND o.create_time >= '2026-01-01';

九、性能测试:数据说话

9.1 测试环境

  • MySQL 8.0.32
  • users 表:100 行
  • orders 表:1,000,000 行
  • 关联字段:user_id
  • 测试查询:SELECT COUNT(*) FROM users u JOIN orders o ON u.id = o.user_id

9.2 测试结果

场景 驱动表 被驱动表索引 JOIN 算法 执行时间
1 users(100行) Index NLJ 0.012s
2 orders(100万行) Index NLJ 2.341s
3 users(100行) Hash Join 0.156s
4 orders(100万行) Hash Join 0.312s
5 users(100行) BNLJ(关闭Hash Join) 45.2s

关键发现

  1. 场景1 vs 场景2 :同样有索引,小表驱动比大表驱动快 195 倍
  2. 场景3 vs 场景4 :Hash Join 下小表构建哈希表比大表构建快 2 倍(大表构建可能触发磁盘溢出);
  3. 场景1 vs 场景3 :有索引的 Index NLJ 比无索引的 Hash Join 快 13 倍,索引仍然是首选;
  4. 场景3 vs 场景5 :Hash Join 比 BNLJ 快 290 倍,8.0 的升级价值巨大。

以上数据为模拟测试环境的典型值,实际性能受硬件、数据分布、buffer pool 大小等因素影响,仅供参考。


十、常见误区与避坑指南

误区一:"LEFT JOIN 左表一定是驱动表"

事实 :在 LEFT JOIN 中,左表确实通常是驱动表(因为需要保留左表全部行),但这不是绝对的。如果左表经过 WHERE 条件过滤后结果集很大,而右表有更优的访问路径,优化器可能会调整。

建议 :始终用 EXPLAIN 确认,不要想当然。

误区二:"驱动表不能走索引"

事实:驱动表也可以走索引。如果驱动表有 WHERE 过滤条件,优化器会优先使用索引过滤,减少驱动表的行数。驱动表走全表扫描只是因为它行数少,全表扫描成本更低。

误区三:"JOIN 表越多越慢,要减少 JOIN"

事实:JOIN 的性能取决于每一步的结果集大小,而不是 JOIN 的数量。如果每一步都能通过索引快速缩小结果集,多表 JOIN 也可以很快。相反,两张大表无索引 JOIN 才是灾难。

误区四:"子查询一定比 JOIN 慢"

事实:MySQL 8.0 优化器会将大多数子查询自动转换为 SEMI JOIN 或 MATERIALIZED 形式,性能与直接 JOIN 相当。但在某些复杂场景下,子查询的执行计划可能不够优,需要具体分析。

误区五:"加了索引就一定走索引"

事实:以下情况会导致索引失效:

  • 对索引列使用函数(WHERE DATE(create_time) = '2026-01-01'
  • 隐式类型转换(INT 列与 VARCHAR 比较)
  • 前导模糊查询(LIKE '%abc'
  • 联合索引不满足最左前缀原则
  • 优化器判断全表扫描更快(小表或高选择性查询)

十一、总结

11.1 核心原则一句话

"小表驱动大表"的本质是:通过减少外层循环次数(即发起查询的轮次),来降低系统调用开销和磁盘 IO;在 Index NLJ 下复杂度为 O(N × logM),N 是线性因子,因此减少 N 的收益远大于减少 M。

11.2 优化 Checklist

#mermaid-svg-avDoKMRffOyPkFjj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-avDoKMRffOyPkFjj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-avDoKMRffOyPkFjj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-avDoKMRffOyPkFjj .error-icon{fill:#552222;}#mermaid-svg-avDoKMRffOyPkFjj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-avDoKMRffOyPkFjj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-avDoKMRffOyPkFjj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-avDoKMRffOyPkFjj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-avDoKMRffOyPkFjj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-avDoKMRffOyPkFjj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-avDoKMRffOyPkFjj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-avDoKMRffOyPkFjj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-avDoKMRffOyPkFjj .marker.cross{stroke:#333333;}#mermaid-svg-avDoKMRffOyPkFjj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-avDoKMRffOyPkFjj p{margin:0;}#mermaid-svg-avDoKMRffOyPkFjj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-avDoKMRffOyPkFjj .cluster-label text{fill:#333;}#mermaid-svg-avDoKMRffOyPkFjj .cluster-label span{color:#333;}#mermaid-svg-avDoKMRffOyPkFjj .cluster-label span p{background-color:transparent;}#mermaid-svg-avDoKMRffOyPkFjj .label text,#mermaid-svg-avDoKMRffOyPkFjj span{fill:#333;color:#333;}#mermaid-svg-avDoKMRffOyPkFjj .node rect,#mermaid-svg-avDoKMRffOyPkFjj .node circle,#mermaid-svg-avDoKMRffOyPkFjj .node ellipse,#mermaid-svg-avDoKMRffOyPkFjj .node polygon,#mermaid-svg-avDoKMRffOyPkFjj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-avDoKMRffOyPkFjj .rough-node .label text,#mermaid-svg-avDoKMRffOyPkFjj .node .label text,#mermaid-svg-avDoKMRffOyPkFjj .image-shape .label,#mermaid-svg-avDoKMRffOyPkFjj .icon-shape .label{text-anchor:middle;}#mermaid-svg-avDoKMRffOyPkFjj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-avDoKMRffOyPkFjj .rough-node .label,#mermaid-svg-avDoKMRffOyPkFjj .node .label,#mermaid-svg-avDoKMRffOyPkFjj .image-shape .label,#mermaid-svg-avDoKMRffOyPkFjj .icon-shape .label{text-align:center;}#mermaid-svg-avDoKMRffOyPkFjj .node.clickable{cursor:pointer;}#mermaid-svg-avDoKMRffOyPkFjj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-avDoKMRffOyPkFjj .arrowheadPath{fill:#333333;}#mermaid-svg-avDoKMRffOyPkFjj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-avDoKMRffOyPkFjj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-avDoKMRffOyPkFjj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-avDoKMRffOyPkFjj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-avDoKMRffOyPkFjj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-avDoKMRffOyPkFjj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-avDoKMRffOyPkFjj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-avDoKMRffOyPkFjj .cluster text{fill:#333;}#mermaid-svg-avDoKMRffOyPkFjj .cluster span{color:#333;}#mermaid-svg-avDoKMRffOyPkFjj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-avDoKMRffOyPkFjj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-avDoKMRffOyPkFjj rect.text{fill:none;stroke-width:0;}#mermaid-svg-avDoKMRffOyPkFjj .icon-shape,#mermaid-svg-avDoKMRffOyPkFjj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-avDoKMRffOyPkFjj .icon-shape p,#mermaid-svg-avDoKMRffOyPkFjj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-avDoKMRffOyPkFjj .icon-shape .label rect,#mermaid-svg-avDoKMRffOyPkFjj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-avDoKMRffOyPkFjj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-avDoKMRffOyPkFjj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-avDoKMRffOyPkFjj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否









JOIN 优化 Checklist

  1. 被驱动表关联字段有索引?
    立即加索引
  2. 驱动表是过滤后的小表?
    检查统计信息/使用 STRAIGHT_JOIN
  3. 关联字段类型一致?
    统一字段类型
  4. 可以用覆盖索引?
    创建覆盖索引避免回表
  5. MySQL 8.0+ 无索引等值 JOIN?
    确认使用 Hash Join
  6. EXPLAIN 验证执行计划
  7. 性能测试确认优化效果

11.3 关键要点回顾

# 要点 说明
1 被驱动表必须有索引 这是 JOIN 优化的第一原则,没有之一
2 小表 = 过滤后的结果集 不是表的总行数,是 WHERE 过滤后参与 JOIN 的行数
3 数学本质是 O(N × logM) N 是线性因子,减少驱动表行数收益最大
4 EXPLAIN 第一行是驱动表 养成每次 JOIN 查询都 EXPLAIN 的习惯
5 STRAIGHT_JOIN 是最后手段 优先更新统计信息,实在不行再强制指定
6 Hash Join 同样需要小表 小表构建哈希表,内存友好、冲突少、探测快
7 Hash Join 只支持等值 JOIN 非等值条件退化为 BNLJ 或其他算法
8 关联字段类型必须一致 隐式类型转换会导致索引失效

理解这条原则背后的数学原理和工程实现,才能在日常 SQL 优化中做出正确的决策,而不是机械地背诵口诀。


相关推荐
cmes_love33 分钟前
港股Level2历史数据:逐笔成交与分钟线
数据库·oracle
自由能燃气设备33 分钟前
燃气热水锅炉/全预混低氮冷凝锅炉哪个品牌好?哪家好?6大商用品牌横评
大数据·数据库·人工智能
智购科技智能售货柜36 分钟前
2026自动售货机设备能耗计量系统:从功率监测到能效分析的工程实践~YH
数据库·人工智能·redis·缓存·架构·perl·symfony
云贝贝贝38 分钟前
PostgreSQL 生产环境备份恢复与高可用实战
数据库·postgresql
程序员-Benothing1 小时前
MySQL 中的 MVCC 是什么?如果没有 MVCC,会有什么影响?
数据库·mysql
Zenova EdgeOS1 小时前
工业网关心跳机制:从 Keepalive 到健康判定的工程实战
大数据·网络·数据库·边缘计算·工业网关
疯狂打码的少年1 小时前
【数据库技术】关系完整性约束(实体/参照/用户定义)
运维·服务器·数据库·笔记
派小汤2 小时前
Harmony2.2.0通过RdbStore实现通用类操作本地SQLite数据库
数据库·sql·sqlite·鸿蒙·鸿蒙系统
程序员-Benothing3 小时前
MySQL 中的事务隔离级别有哪些?默认的事务隔离级别是什么?为什么选择这个级别?
数据库·mysql