被驱动表的ON字段必须建索引,因为MySQL默认用Nested-Loop Join算法,驱动表每输出一行,就要在被驱动表中查找匹配行;若无索引则触发全表扫描,导致扫描量剧增、性能崩溃,EXPLAIN中type为ALL即为典型征兆。

JOIN 查询慢,往往不是因为写了 JOIN,而是关联字段没索引——这是最常见、也最容易被忽略的性能瓶颈。
为什么被驱动表的 ON 字段必须建索引?
数据库执行 JOIN 时,通常选一个表作驱动表(外表),另一个作被驱动表(内表)。驱动表输出多少行,就要去被驱动表里查多少次匹配行。如果没有索引,每次查找都得全表扫描——1000 行驱动,就得扫被驱动表 1000 遍。
-
INNER JOIN users u ON u.id = o.user_id中,若orders.user_id没索引,orders表就会被反复全扫 -
LEFT JOIN更敏感:左表全量必须保留,右表哪怕只有一行匹配不上,也要查完整张表找 NULL —— 没索引等于直接放弃优化 - 用
EXPLAIN看执行计划,type是ALL就说明被驱动表正在全表扫描;变成ref或eq_ref才算走对了路
user_id 字段建什么索引才真正生效?
不是随便加个索引就行。关键看查询是否能命中索引结构,尤其要注意最左前缀和类型一致性。
- 单列索引
INDEX(user_id)最稳妥,适用于纯等值连接:ON u.id = o.user_id - 如果还常带范围条件(如
AND o.created_at > '2025-01-01'),可建联合索引INDEX(user_id, created_at),但顺序不能颠倒 - 避免隐式转换:
user_id是INT,但ON u.id = CAST(o.user_id AS CHAR)会让索引彻底失效 - 字符集不一致也会断掉索引:比如左表用
utf8mb4_unicode_ci,右表用utf8mb4_general_ci,MySQL 会拒绝走索引
为什么主键索引不等于 JOIN 加速?
主键自动有聚集索引,但它只加速主键本身的查找和范围扫描。如果 JOIN 条件用的是外键字段(比如 orders.user_id),而这个字段没单独建索引,聚集索引就帮不上忙。
-
users.id是主键 → 聚集索引天然存在 →users作为被驱动表时效率高 -
orders.user_id不是主键 → 即使orders.id是聚集索引,user_id还是得靠二级索引定位 → 没索引就只能回表或全扫 - 实测中,给
orders.user_id加INDEX后,EXPLAIN的rows常从几十万降到几百,Extra里消失Using join buffer
小表驱动大表时,索引还能省吗?
不能。即使驱动表只有 10 行,如果被驱动表是千万级且没索引,照样要扫千万行 × 10 次——I/O 和 CPU 开销依然爆炸。
- 哈希连接(MySQL 8.0+)可以绕过索引,但只适用于右表足够小(比如状态码表、分类表),且内存充足
- 一旦被驱动表超过几万行,哈希连接可能退化为块嵌套循环(
Block Nested Loop),此时没索引仍会触发磁盘临时表 - 真正安全的做法:不管表大小,只要它出现在
ON右侧,就给关联字段建索引
EXPLAIN 输出里的 key 列为空、type 是 ALL、或者 Extra 出现 Using where 而不是 Using index,都是危险信号——这时候建了索引也没用。

















