不推荐使用SELECT *,因其导致覆盖索引失效、强制回表增加IO、字段变更引发应用错位、网络与内存开销剧增,且掩盖数据访问意图,严重危害性能、稳定性和可维护性。

生产环境里用 SELECT * 不是语法错误,而是把性能、稳定性和可维护性全押在表结构不变的假设上——这个假设在线上几乎每天都在被打破。
覆盖索引直接失效,回表成必然
MySQL 的覆盖索引(Using index)要求查询所需所有字段都落在同一个二级索引的叶子节点里。SELECT * 一出现,只要表里有任何一个字段没被该索引包含(比如 log TEXT、config JSON 或新增的 updated_by),优化器就只能放弃覆盖索引,强制回表。
-
EXPLAIN中Extra字段从Using index变成Using where,就是最明确的信号 - 哪怕
WHERE user_id = ?走的是INDEX(user_id),也要先查索引拿主键,再回聚簇索引捞整行——两次 B+ 树查找 + 随机 IO - InnoDB 对溢出页的额外读取(off-page read)会让 IO 次数从 2 升到 3,buffer pool 命中率低时尤为明显
字段变更立刻击穿应用层
表结构一旦变化,SELECT * 返回的列集合就变了,但应用代码往往还按老逻辑处理。
- JDBC 中用
ResultSet.getObject(2)按位置取值?加一列后全部错位,抛Column index out of range - MyBatis 的
resultMap若未显式映射字段,新增一个NOT NULL列且无默认值,直接触发SQLException - 视图用
SELECT *创建,后续ALTER TABLE ADD COLUMN不会自动同步,查出来永远少一列
网络与内存开销被严重低估
一行数据体积不是由业务需要决定的,而是由表里最宽的字段决定的。你只用 id 和 nickname,SELECT * 却可能拖着几 MB 的 profile_json 和 avatar_blob 一起走。
- 单行从 200B 膨胀到 5KB,1000 行就是 5MB 网络传输——即使 DB 和应用同机部署,也要走 loopback TCP 栈
- ORM 如 Hibernate 的
findAll()会为每个字段创建对象属性,哪怕你只访问其中两个,GC 压力也翻倍 - 分页场景如
LIMIT 10000, 20,MySQL 仍要构造并丢弃前 10000 行完整记录,CPU 和内存纯浪费
真正难的不是多敲几个字段名
而是得想清楚:这一查到底要什么数据、谁在消费、字段生命周期多长、下次加列时会不会崩。SELECT * 让人跳过所有这些判断,只留下一个看似省事、实则高危的惯性动作。它掩盖的从来不是语法问题,而是数据访问意图的模糊——你到底要什么字段、为什么需要、会不会变。

















