JOIN字段无索引必然导致慢查询,须为所有关联字段建索引;驱动表应选过滤后结果集小的表;先WHERE过滤再JOIN更高效;避免ON中函数操作;多表JOIN超3个宜拆分或缓存。

JOIN字段没索引?90%的慢查询都栽在这一步
只要ON或WHERE里用到的关联字段没索引,数据库大概率会全表扫描——尤其是大表参与JOIN时,性能断崖式下跌。这不是“可能慢”,而是“一定慢”,且数据量越大越明显。
- 必须给所有
JOIN条件字段建索引,包括左表和右表,不能只建一边 - 优先用主键或唯一索引;若需复合索引,等值字段(如
user_id = ?)放最左,范围字段(如create_time > ?)放后面 - 别信“字段类型一样就自动走索引”——隐式类型转换(比如
INT列和VARCHAR字符串比较)会让索引完全失效 - 用
EXPLAIN看执行计划,重点盯type列:要是ALL或index,基本就是没走对索引
小表驱动大表不是看物理大小,而是看过滤后结果集
很多人死记“小表放左边”,结果把100万行但带WHERE status = 'done'(只剩500行)的大表硬塞右边,反而让优化器选错驱动表,中间结果集爆炸。
- 驱动表的选择依据是“经过
WHERE过滤后的估算行数”,不是原始COUNT(*) -
EXPLAIN里的rows列才是关键参考:驱动表对应行数应显著小于被驱动表 - 统计信息过期会导致
rows严重失真,定期跑ANALYZE TABLE(MySQL)或VACUUM ANALYZE(PostgreSQL) - 实在不确定,可用
STRAIGHT_JOIN强制顺序,但仅限调试或明确知道优化器出错时使用
先过滤、再JOIN,顺序错了性能差十倍
写成FROM orders JOIN users ON ... WHERE orders.created_at > '2025-01-01',和写成FROM (SELECT * FROM orders WHERE created_at > '2025-01-01') o JOIN users ...,实际执行路径可能完全不同——前者可能先做笛卡尔积再过滤,后者直接压缩小表。
- 把高选择性
WHERE条件尽量提前,套在子查询或CTE里再JOIN,比堆在最后更可控 - 避免在
ON子句里对字段做函数操作,例如ON DATE(o.create_time) = DATE(u.join_date)——索引直接作废 - 如果业务允许,考虑冗余字段(如把
DATE(create_time)单独存为create_date并建索引),比函数索引更稳定
JOIN太多?别硬扛,拆或缓存才是正解
5张表以上JOIN,即使每张都有索引,执行计划也极易失控。优化器在多表组合爆炸下大概率选错路径,Using temporary和Using filesort频繁出现,说明内存撑不住了。
- 超过3个表的JOIN,优先评估是否能用应用层分步查:先查主表ID列表,再用
IN批量捞关联数据 - 高频、低更新的关联结果,可物化到宽表或用Redis缓存,避免每次请求都重算
- 别迷信“一条SQL搞定一切”,
SELECT *+ 多表JOIN +ORDER BY+LIMIT混在一起,往往是慢查询温床
真实场景里,最常被忽略的是“过滤后结果集大小”和“统计信息是否新鲜”。这两点不确认,调索引、换顺序、加提示都像蒙眼开车。










