小表驱动大表------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 的订单
结果集
执行步骤:
- 全表扫描 users 表,取出 100 行(成本极低)
- 对每个 user_id,通过 orders 表的 B+ 树索引快速定位对应订单
- 总共发起 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+树索引定位
对应用户
结果集
执行步骤:
- 全表扫描 orders 表,取出 100 万行(成本高)
- 对每个订单的 user_id,去 users 表查找匹配用户
- 总共发起 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.id 是 INT,而 orders.user_id 是 VARCHAR,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;
⚠️ 风险提示:
- 强制指定可能因数据分布变化导致性能退化,需配合
ANALYZE TABLE定期更新统计信息;- 优先尝试更新统计信息让优化器自己选对,
STRAIGHT_JOIN是最后手段;- 加了
STRAIGHT_JOIN后要做性能回归测试。
8.4 EXISTS vs IN:子查询的驱动表选择
在子查询场景中,EXISTS 和 IN 的执行逻辑不同,直接影响驱动表选择。
#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 中,IN和EXISTS的性能差异已经大幅缩小。但在复杂查询或统计信息不准时,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 vs 场景2 :同样有索引,小表驱动比大表驱动快 195 倍;
- 场景3 vs 场景4 :Hash Join 下小表构建哈希表比大表构建快 2 倍(大表构建可能触发磁盘溢出);
- 场景1 vs 场景3 :有索引的 Index NLJ 比无索引的 Hash Join 快 13 倍,索引仍然是首选;
- 场景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
- 被驱动表关联字段有索引?
立即加索引 - 驱动表是过滤后的小表?
检查统计信息/使用 STRAIGHT_JOIN - 关联字段类型一致?
统一字段类型 - 可以用覆盖索引?
创建覆盖索引避免回表 - MySQL 8.0+ 无索引等值 JOIN?
确认使用 Hash Join - EXPLAIN 验证执行计划
- 性能测试确认优化效果
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 优化中做出正确的决策,而不是机械地背诵口诀。