应只SELECT必要字段、WHERE条件勿误放ON中、用覆盖索引避免回表、小表驱动大表且驱动表需有索引。

SELECT 中只写真正需要的字段,别用 *
用 * 看起来省事,但数据库得把整行所有字段从磁盘读出来、经过网络传给客户端、再被应用层丢弃掉——这些字段哪怕你压根没用,也全程参与了 I/O、内存、网络开销。尤其当表里有 TEXT、JSON、BLOB 或宽字段(比如 50 列)时,性能下降会非常明显。
实操建议:
- JOIN 前先列清楚业务逻辑真正要什么:是只需要
user.id和order.total?那就只 SELECT 这两个 - 如果用 ORM,确认它生成的 SQL 没偷偷加
*或冗余字段(比如 Django 的.values()/.only()要显式指定) - 对宽表 JOIN,宁可多写几遍字段名,也别依赖
SELECT *—— 后续加字段时,*会无声无息拖慢查询
WHERE 条件写在 JOIN 后,别错放到 ON 里过滤主表
ON 是定义关联逻辑的,WHERE 才是最终筛选结果的。把本该在 WHERE 的条件(比如 user.status = 'active')误塞进 ON,会导致 LEFT JOIN 变成隐式 INNER JOIN,或漏掉本该保留的左表记录——这不是字段多不多的问题,是逻辑错误。
常见错误现象:
- LEFT JOIN users u ON u.id = o.user_id AND u.status = 'active' → 如果用户 status 不是 active,这条 user 记录就“消失”了,哪怕你只想查订单,也拿不到对应用户信息
- 正确做法是:LEFT JOIN users u ON u.id = o.user_id WHERE u.status = 'active'(注意:这会让 LEFT JOIN 失效,等价于 INNER;若真要保留左表空值,WHERE 应写成
WHERE (u.status = 'active' OR u.status IS NULL))
用覆盖索引避免回表,让 SELECT 字段全落在索引里
如果 SELECT 的字段都能被某个索引“覆盖”,MySQL/PostgreSQL 就不用回主键索引捞数据——直接从二级索引里读完走人。这对 JOIN 尤其关键:每多一次回表,就是多一次随机 I/O。
实操建议:
- 对高频 JOIN 查询,建联合索引时把 SELECT 字段和 JOIN 条件字段一起包含进去,顺序按“等值条件 → 排序/范围条件 → SELECT 字段”组织
- 例如:SELECT u.name, u.email FROM users u JOIN orders o ON u.id = o.user_id WHERE u.deleted = 0;可建索引
INDEX idx_user_cover (deleted, id, name, email) - 用
EXPLAIN看Extra是否出现Using index;如果还有Using where; Using index condition,说明没完全覆盖
小表驱动大表 + 驱动表字段必须有索引
JOIN 执行时,数据库通常选一个表做“驱动表”(外层循环),另一个做“被驱动表”(内层循环查索引)。如果驱动表是 100 万行的大表,而被驱动表缺索引,就会触发 100 万次全表扫描——这时候减少字段根本没用,IO 已经爆炸。
判断与优化要点:
- 用
EXPLAIN看rows和type:驱动表rows应尽量小,被驱动表type最好是ref或eq_ref,不是ALL - 如果业务上能确定某张表永远更小(比如
status_codes表只有几十行),就把它放 JOIN 左侧,并确保 ON 条件字段有索引 - 别迷信“小表驱动大表”的字面意思——实际要看过滤后的行数,不是原表大小。先 WHERE 再 JOIN,比先 JOIN 再 WHERE 更有效
字段精简只是冰山一角;真正的瓶颈往往藏在驱动顺序、索引覆盖和条件位置里。改一个字段名解决不了问题,但挪一行 WHERE 就可能快十倍。

















