被驱动表无索引时,MySQL只能全表扫描;LEFT JOIN右表(被驱动表)若连接字段无索引,每从左表取1行就要扫描整个右表,形成O(n×m)嵌套循环,导致EXPLAIN中type为ALL、key为NULL、Extra出现Using join buffer,性能急剧下降。

被驱动表没索引时,MySQL只能全表扫描
当 LEFT JOIN 的右表(即被驱动表)没有索引,而连接条件又落在该表某列上(比如 ON user.id = order.user_id),MySQL 每从左表取出一行,就要完整扫描一遍右表去匹配——哪怕只查 1 行,也要读取右表全部千万行。这种嵌套循环(Simple Nested-Loop Join)的 IO 成本是驱动表行数 × 被驱动表行数,极易拖垮查询。
常见错误现象:EXPLAIN 输出中被驱动表的 type 是 ALL,rows 显示为整张表行数,key 为 NULL。
- 即使驱动表只有 100 行,被驱动表 1000 万行,也要做 100 × 1000 万次比较和磁盘读取
- 索引不是“可选优化”,而是避免暴力扫描的刚性需求
-
INNER JOIN同样适用:哪怕 MySQL 自动选小表作驱动表,大表若无索引,仍会全表扫
LEFT JOIN 必须在右表建索引,RIGHT JOIN 则相反
连接类型直接决定哪边是被驱动表:LEFT JOIN 左表驱动、右表被驱动;RIGHT JOIN 右表驱动、左表被驱动。优化点永远落在被驱动表上,而不是“哪个表看起来更关键”。
典型误操作:给 LEFT JOIN 的左表(驱动表)加了索引,右表没加,结果 EXPLAIN 里 Extra 还是出现 Using where; Using join buffer (Block Nested Loop)——说明右表仍在被暴力匹配。
-
LEFT JOIN users u ON u.id = o.user_id→ 在o.user_id上建索引(test_order.user_name或user_id字段) -
RIGHT JOIN users u ON u.id = o.user_id→ 在u.id上建索引 - 不要依赖“主键自动有效”:如果连接条件用的是非主键字段(如
user_name),主键索引完全无效
千万级表加索引本身就有风险,不能直接 ALTER TABLE ADD INDEX
在千万级表上直接建索引,可能触发长时间锁表、磁盘爆满或写入阻塞。这不是理论问题,而是线上真实故障高发场景。
比如给一张 800 万行的 orders 表在 status 字段加单列索引,即使语句成功,也可能导致业务订单写入延迟飙升、监控告警连发。
- 优先用
ALGORITHM=INPLACE, LOCK=NONE(MySQL 5.6+ 支持),但仅限部分 DDL 类型,且需确认存储引擎支持 - 低峰期执行,并配合
pt-online-schema-change等工具做灰度变更 - 避免对低选择性字段(如
status、is_deleted)单独建索引;应作为联合索引的后缀列,例如INDEX idx_user_status (user_id, status)
子查询作被驱动表时,索引基本失效
当 LEFT JOIN 右侧是个子查询(如 (SELECT ... FROM t_emp e JOIN t_dept d ON ...)),这个结果集是临时虚表,无法提前建索引。MySQL 只能把它物化后全表扫描匹配,性能断崖式下跌。
常见陷阱:EXPLAIN 显示子查询那行的 type 是 DERIVED 或 MATERIALIZED,且后续关联行数暴增。
- 把子查询“拉平”:尽量用实体表替代子查询,或将子查询逻辑提前到驱动表一侧
- 实在要用,确保子查询结果集极小(
- 注意
JOIN顺序:MySQL 不保证子查询一定当被驱动表,但一旦它出现在ON右侧,就大概率成为瓶颈



















