select *让覆盖索引失效,因其要求查询所有字段(含text/blob等不可索引列)均存在于同一索引中,而innodb不存储大字段于索引页,必然触发回表,explain的extra绝不会出现using index。

SELECT * 为什么让覆盖索引直接失效
MySQL 的覆盖索引(Using index)要求查询所需的所有字段都包含在同一个二级索引的叶子节点里。一旦用了 SELECT *,只要表里存在任意一个未被该索引覆盖的字段(比如 TEXT、BLOB、JSON,或后来新增的 config_data),优化器就无法满足覆盖条件,只能放弃索引,强制回表。
典型信号是 EXPLAIN 中 Extra 字段从 Using index 变成 Using where; Using index condition 或干脆 Using where。哪怕 WHERE 走的是 INDEX(user_id, status),只要表里有 log TEXT,就得先查索引拿主键,再回聚簇索引捞整行——两次 B+ 树查找 + 随机 IO。InnoDB 对溢出页的额外读取会让实际 IO 次数从 2 升到 3,buffer pool 命中率低时尤为明显。
字段变更后应用层静默错位
用 SELECT * 的代码,把字段顺序和数量当成了“运气”,而不是契约。
- JDBC 中写
ResultSet.getObject(2)按位置取值?加一列后,原本第 2 列变成第 3 列,所有取值全错位,抛Column index out of range - MyBatis 的
resultMap若没显式映射字段,新增一个NOT NULL列且无默认值,直接触发SQLException - 视图用
SELECT * FROM t创建,后续ALTER TABLE t ADD COLUMN x VARCHAR(32),视图定义不会自动更新,查出来永远少一列
网络、内存、CPU 开销被严重低估
你只用 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 * 让人跳过所有这些判断,只留下一个看似省事、实则高危的惯性动作。线上故障日志里常见的 Cannot convert value 'xxx' to type 'java.lang.Integer',根因往往就是字段类型或顺序变了,而 SQL 还在盲目返回全部。
最隐蔽的风险,是你直到加了一列、改了一次类型、换了一次 ORM 版本,才第一次看见它报错——而那时,问题早已在线上跑了好几天。











