select * 在生产环境不该默认使用,因其导致回表查询、增加网络传输与内存开销、降低缓存效率,并在join、子查询、union等场景引发字段冲突、执行计划失真及合规风险。

SELECT * 在生产环境里不是“不能用”,而是“不该默认用”——它会悄悄放大资源消耗、掩盖索引失效、拖慢查询响应,尤其在数据量上涨后问题会集中爆发。
为什么 SELECT * 会让查询变慢
MySQL 无法利用覆盖索引(covering index)时,必须回表查全字段。哪怕你只在 WHERE 条件里用了 id 和 status,但 SELECT * 会强制引擎先走索引定位行,再回到聚簇索引里把所有字段捞一遍。
- 网络传输更多:多传几十个字段,尤其含
TEXT、BLOB或长VARCHAR时,带宽和序列化开销明显上升 - 内存压力更大:MySQL 的 sort buffer、join buffer 都按实际返回字段长度分配,SELECT * 容易触发磁盘临时表(
Using temporary; Using filesort) - 缓存效率更低:查询结果无法被 Query Cache(若启用)或应用层缓存有效复用,因为字段顺序、NULL 值分布、甚至列名大小写都可能让缓存键不一致
SELECT * 在 JOIN 场景下更危险
多表 JOIN 时,SELECT * 会把所有表的字段都拉出来,极易出现字段名冲突(比如两个表都有 id)、隐式类型转换、以及应用层取值错位。
- ORM 框架如 MyBatis、Hibernate 默认映射字段名,SELECT * + JOIN 可能导致
id被后一个表覆盖,业务逻辑静默出错 - 即使加了表别名,像
SELECT u.*, o.order_no FROM user u JOIN order o ON u.id = o.user_id,一旦user表新增字段(比如avatar_url),就可能意外把几 MB 的图片 URL 拉进结果集 - 数据库优化器对 SELECT * 的执行计划预估更粗糙,
rows和filtered字段参考价值下降
哪些情况真的不能用 SELECT *
以下场景中,SELECT * 不仅低效,还可能直接报错或引发严重后果:
- 子查询中使用:
SELECT * FROM (SELECT * FROM user) t—— MySQL 8.0+ 会拒绝无别名的子查询,且嵌套越深越难维护 - UNION 查询:
SELECT * FROM a UNION SELECT * FROM b—— 两表字段数、类型、顺序必须严格一致,稍有变更就中断 - 导出或同步任务:ETL 工具依赖明确 schema,SELECT * 会导致目标表字段错位、截断、或导入失败
- 审计/合规要求字段最小化:GDPR、等保2.0 明确要求“仅收集必要字段”,SELECT * 属于典型违规操作
替代方案要具体到字段,而不是“尽量少选”
真正有效的做法是:**根据业务上下文,显式写出需要的字段,并确保它们有索引支撑**。
- 列表页:只取
id,title,updated_at—— 这三个字段建联合索引,就能避免回表 - 详情页:明确列出
id,content,author_name,category_name,并检查content是否真需每次加载(考虑延迟加载) - 统计类查询:坚决不用 SELECT *,连
SELECT COUNT(*)都比SELECT COUNT(id)更安全(前者不跳过 NULL,语义更确定)
最常被忽略的一点是:**字段顺序也有影响**。把高频过滤字段(如 tenant_id、status)放在联合索引最左,再把 SELECT 中的字段按索引顺序排列,才能最大化覆盖索引收益。










