select * 在 join 中特别伤性能,因其强制读取所有列导致 i/o 剧增、覆盖索引失效、必须回表查询,并加剧排序/临时表开销;orm 默认全字段更恶化问题。

为什么 SELECT * 在 JOIN 中特别伤性能
不是“多传几个字段”那么简单。MySQL 要为 SELECT * 读取所有列对应的数据页,哪怕你只用其中一两个字段——尤其当表里有 TEXT、BLOB 或宽字段时,IO 成倍增加;排序、分组、临时表操作也更慢;ORM 默认拖全字段进来,更是雪上加霜。
更关键的是:SELECT * 直接让覆盖索引失效。只要结果里包含非索引列,MySQL 就必须回表查聚簇索引,彻底废掉“不用回表”的优势。
怎么写 JOIN 才能只取真正需要的字段
核心原则:显式列出字段,且字段要能被已有索引覆盖。不是建了索引就行,是字段、顺序、类型都要对得上。
- 用别名避免歧义:
SELECT u.id, u.name, o.order_no, o.amount FROM users u JOIN orders o ON u.id = o.user_id - 检查是否无意引入重复字段(比如两张表都有
created_at,又没指定别名,导致结果集冲突或冗余) - 对大文本字段,考虑异步加载或用
SUBSTRING(content, 1, 200)截断预览 - LEFT JOIN 右表字段若不在索引中,照样回表——哪怕实际值全是
NULL
哪些字段组合能让覆盖索引生效
覆盖索引生效的前提是:SELECT 的所有字段 + JOIN/WHERE 用到的字段,全部落在同一个联合索引里。
例如常见场景 orders JOIN users:
- 如果只查
orders.id, orders.status, users.name,就在orders上建(status, id),在users上建(id, name) -
JOIN条件列(如orders.user_id)必须是索引最左前缀,否则无法驱动索引扫描 - 复合索引顺序很重要:
(a, b, c)能覆盖SELECT a,b WHERE a=1 AND b>5,但不能覆盖SELECT b,c WHERE c=1 - ON 条件里别用函数,比如
ON u.id = CAST(o.user_id AS SIGNED),会导致索引失效
EXPLAIN 怎么看字段是否真被索引覆盖
运行 EXPLAIN FORMAT=TREE 或传统 EXPLAIN,重点盯三处:
-
type是ref或range是好信号 -
Extra列出现Using index且没有Using filesort、Using temporary,说明覆盖成功 - 如果
Extra是Using where; Using index; Using join buffer,说明索引用了,但没完全覆盖 - 如果出现
Using where; Using index condition; Using temporary,基本已回表
真正容易被忽略的,是字段顺序和 NULL 处理——索引不存 NULL 值,但优化器对 IS NULL 过滤是否走索引,依赖列定义和 MySQL 版本,旧版本尤其容易踩坑。










