答案是被驱动表的JOIN字段必须有索引。因为MySQL默认采用Nested-Loop Join算法,驱动表每读一行,需在被驱动表中查找匹配行;若被驱动表(尤其是大表)无索引,则触发全表扫描,导致行扫描量剧增、性能崩溃;EXPLAIN中type为ALL或Extra含“Using join buffer”即为典型征兆,而正确索引应使type变为ref或eq_ref。

大表没索引,JOIN就变全表扫描
因为MySQL的JOIN默认用Nested Loop算法:驱动表每读一行,就要去被驱动表里找匹配行。如果被驱动表(通常是大表)没索引,那就只能逐行比对——100万行 × 1万次查找 = 100亿次行扫描,IO和CPU直接拉满。
常见错误现象:EXPLAIN 显示 type: ALL 或 Extra: Using join buffer,说明被驱动表正在全表扫描。
- INNER JOIN 中,优化器可能选错驱动表,但只要被驱动表没索引,性能一定崩
- LEFT JOIN 固定左表为驱动表,右表(被驱动表)必须有索引,否则右表全扫
- 字段类型不一致(比如
INTvsVARCHAR)、字符集不同(utf8mb4vslatin1),也会让已有索引失效
不是“大表上建索引”,而是“被驱动表的JOIN字段必须有索引”
关键不是物理大小,而是执行计划里的角色。用 EXPLAIN 看第一行的表名,就是驱动表;剩下的是被驱动表——索引必须落在被驱动表的JOIN列上。
例如:SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id,users 是驱动表,orders.user_id 是被驱动字段,这里必须建索引。
-
orders(user_id)是最基础的单列索引 - 如果还有
WHERE o.status = 'paid',考虑复合索引orders(user_id, status),但注意最左前缀:user_id必须在前 - 别在
orders上只建status索引——它对JOIN无用,除非你把它和user_id组成复合索引
小表驱动大表 ≠ 写SQL时把小表放前面就完事
INNER JOIN 的表顺序只是提示,优化器可能无视;LEFT/RIGHT JOIN 的方向才真正锁定驱动关系。靠写法“骗过”优化器不靠谱,得靠 STRAIGHT_JOIN 或强制索引。
- 想确保小表驱动:用
SELECT STRAIGHT_JOIN ... FROM small_table s JOIN large_table l ON ... - 确认被驱动表走索引:
EXPLAIN中对应行的type应为ref、eq_ref或const,不能是ALL或index - 如果大表数据量超千万,光靠B+树索引还不够,要考虑分区、分库,或改用应用层拼装(先查小表ID列表,再用
IN分批查大表)
为什么有时候加了索引也没用?
索引建了≠能用。最常踩的坑是隐式转换:JOIN两边字段类型或字符集不一致,MySQL会自动转类型,导致索引失效。
比如:users.id 是 BIGINT,orders.user_id 是 VARCHAR(20),哪怕值看起来一样,MySQL也必须把每个 user_id 转成数字再比——索引彻底作废。
- 用
SHOW CREATE TABLE对比两字段的DATA_TYPE、COLLATION、CHARACTER_SET - 修复方法:统一改成
BIGINT UNSIGNED,或用CAST(o.user_id AS SIGNED)强制转换(不推荐,只是临时绕过) - 另一个隐形杀手:在JOIN条件里写函数,比如
ON UPPER(u.email) = UPPER(o.email)—— 索引无法命中
实际调优永远从 EXPLAIN 开始,而不是凭感觉加索引。被驱动表字段是否走索引,一眼就能看出来;但类型不一致这种问题,不对比表结构根本发现不了。


















