select * 在生产环境会引发性能、稳定性和可维护性问题:导致覆盖索引失效而回表、字段变更击穿应用代码、增大网络与内存开销,并掩盖数据需求分析。

生产环境里用 SELECT * 不是语法错误,而是把性能、稳定性和可维护性全押在“表结构永远不变”这个假设上——而这个假设在线上几乎每天都在被打破。
覆盖索引失效导致回表是最快暴露的性能问题
MySQL 的覆盖索引(Using index)要求查询所需所有字段都落在同一二级索引的叶子节点里。SELECT * 一出现,只要表里有任何一个字段没被该索引包含(比如 TEXT、BLOB、新增的 config_data JSON),优化器就只能放弃覆盖索引,强制回表。
-
EXPLAIN中Extra字段从Using index变成Using where; Using index condition或直接NULL,就是最明确的信号 - 即使
WHERE走的是INDEX(user_id, status),只要表里有log TEXT,就得先查索引拿主键,再回聚簇索引捞整行——两次 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 * 让人跳过所有这些判断,只留下一个看似省事、实则高危的惯性动作。











