驱动表应选过滤后行数最少的表,LEFT JOIN需在右表关联字段建索引且类型一致,INNER JOIN应以EXPLAIN为准判断实际驱动表,被驱动表无索引将退化为BNL导致性能雪崩。

驱动表选得不对,再好的索引也救不了查询性能。 MySQL 的 JOIN 本质是循环匹配,外层循环次数直接决定整体开销——驱动表行数越少,被驱动表的索引查找次数就越少。优化核心不是“加不加索引”,而是“谁驱动、谁被驱动、索引加在哪”。
LEFT JOIN 必须把索引建在右表的关联字段上
LEFT JOIN 的左表固定为驱动表,右表是被驱动表。驱动表无论如何都要全扫一遍(哪怕有索引,也只是加速 WHERE 过滤,不减少循环次数);而被驱动表每次匹配都依赖索引快速定位,否则就是全表扫描 × 驱动表行数。
- 错误做法:
ALTER TABLE class ADD INDEX idx_card(card)(只给左表加索引),EXPLAIN 仍显示type: ALL在右表上 - 正确做法:
ALTER TABLE book ADD INDEX idx_card(card)(右表建索引),EXPLAIN 中右表type变为ref或eq_ref - 类型必须一致:如果
class.card是VARCHAR(32),book.card是BIGINT,即使右表有索引也会失效,触发隐式转换导致全表扫描
INNER JOIN 不要靠表顺序猜驱动表,用 EXPLAIN 看实际执行计划
优化器会自动选择成本更低的表作为驱动表,但它的判断基于统计信息和索引情况,并非简单看行数或书写顺序。你写的 SELECT * FROM A INNER JOIN B ON ... 和 SELECT * FROM B INNER JOIN A ON ... 很可能走完全一样的执行路径。
- 关键动作:先跑
EXPLAIN,重点看table列哪一行在前(驱动表)、type列是否为ALL(被驱动表没走索引就危险) - 如果只有一张表有索引,优化器大概率把它设为被驱动表——因为驱动表无论如何都要全读,而被驱动表能靠索引省下大量 I/O
- 想强制指定?可用
STRAIGHT_JOIN,例如SELECT * FROM class STRAIGHT_JOIN book ON class.card = book.card,此时class一定为驱动表
Nested-Loop Join 下,被驱动表没索引 = 性能雪崩
MySQL 默认使用 Index Nested-Loop Join(INLJ),前提是被驱动表关联字段有可用索引。一旦缺失,就会退化为 Block Nested-Loop Join(BNL),依赖 join_buffer 缓存驱动表数据再批量比对——这看似缓解了磁盘 I/O,但内存压力陡增,且无法利用 B+ 树的 O(log N) 查找优势。
- 典型症状:
Extra列出现Using join buffer (Block Nested Loop),同时rows值异常高 - 查 Buffer 大小:
SHOW VARIABLES LIKE 'join_buffer_size',默认才 256KB,大结果集极易溢出到磁盘临时表 - 真正高效的 INLJ 表现是:驱动表
rows小 + 被驱动表type为ref/eq_ref+Extra无Using join buffer
最常被忽略的一点:驱动表的“小”,是指 WHERE 条件过滤后的结果集大小,不是原始表行数。一张千万级订单表,加了 WHERE status = 'paid' AND created_at > '2026-04-01' 后只剩几十行,它就是事实上的“小表”。别光盯着 SELECT COUNT(*) 看总行数。











