JOIN查询慢主因是索引未建在驱动表ON字段上:左表为驱动表需其ON字段有索引,类型须一致,联合索引需匹配最左前缀,WHERE/ORDER BY字段应纳入联合索引,LEFT JOIN与INNER JOIN索引策略不同,且需定期ANALYZE TABLE更新统计信息。

JOIN查询慢,八成是索引没打在关键位置上——不是不加索引,而是加错了字段、顺序或类型。
驱动表的ON字段必须有索引
MySQL默认用左表作驱动表(尤其LEFT JOIN),优化器会先扫它,再拿每行去右表找匹配。如果orders.user_id没索引,就会全表扫描orders,哪怕users.id有索引也白搭。
-
EXPLAIN里看到驱动表的type=ALL或key=NULL,就是铁证 - 单列索引足够:直接
CREATE INDEX idx_orders_user_id ON orders(user_id) - 别指望靠
INDEX(created_at, user_id)来支撑ON o.user_id = u.id——最左前缀不匹配,user_id压根走不上
被驱动表的ON字段必须类型一致且有索引
就算users.id是主键,如果orders.user_id是VARCHAR(20)而users.id是INT,MySQL会把整列转成字符串比对,索引失效。
- 用
SHOW CREATE TABLE核对两边字段类型、符号性(SIGNED/UNSIGNED)、字符集和COLLATE -
users.id是主键,但orders.user_id必须单独建索引——主键索引只对本表生效 - 避免
ON UPPER(u.name) = UPPER(o.name)这类写法;真要忽略大小写,统一用COLLATE utf8mb4_0900_as_cs,别套函数
WHERE和ORDER BY字段得塞进联合索引里
光让JOIN字段走索引还不够。如果查询带WHERE u.status = 'active'或ORDER BY o.amount DESC,这些字段没被覆盖,照样触发Using temporary; Using filesort。
- 联合索引顺序按“等值 → 范围 → 排序”排:
INDEX(status, created_at, amount)支持WHERE status = ? AND created_at > ? ORDER BY amount DESC -
IN算范围条件,它后面的字段无法用于排序;别把ORDER BY字段塞在IN字段后面 - 字段数别超3–4个;
VARCHAR(500)这种宽字段加进索引,体积暴涨,维护成本高
LEFT JOIN和INNER JOIN的索引策略不一样
LEFT JOIN强制左表为驱动表,右表索引再好,左表没索引就崩盘;INNER JOIN允许优化器换驱动表,但前提是两张表的ON字段都有索引,否则它不敢乱动。
-
LEFT JOIN users u ON o.user_id = u.id WHERE u.deleted = 0——这个WHERE会让左连接退化成内连接,且u.deleted没进索引的话,users表可能全扫 - 想保留LEFT语义又过滤右表,把条件写进
ON:LEFT JOIN users u ON o.user_id = u.id AND u.deleted = 0 -
STRAIGHT_JOIN慎用;只有当你明确知道小结果集表是谁,且EXPLAIN确认优化器选错了顺序时才干预
最容易被忽略的点:统计信息过期。ANALYZE TABLE不是可选项,是必做项——尤其大表数据批量导入后。优化器靠它判断哪张表该当驱动表,没准数据,索引建得再好也白搭。

















