select *必然破坏覆盖索引且引发回表,因text/blob等溢出字段不进索引、orm易越权、列序错位风险高,仅限临时调试等三类场景可用。

EXPLAIN里出现Using where就说明SELECT *已破坏覆盖索引
只要执行计划中Extra字段显示Using where而非Using index,基本能确定是SELECT *导致的回表。覆盖索引生效的前提是所有被查询字段都落在同一二级索引的叶子节点里,而TEXT、BLOB、超长VARCHAR(>728字节)根本不会进任何索引——哪怕你建了KEY idx_name_status (name, status),只要表里存在这类字段,SELECT *就必然触发回表。
常见误判点:
- type仍是
ref或range≠走覆盖索引;关键看Extra - 优化器可能因成本评估放弃索引,直接选全表扫描——顺序I/O有时比多次随机I/O更快
-
SHOW CREATE TABLE查不到溢出字段是否被索引忽略,只能靠EXPLAIN实测
单行数据从200B膨胀到10KB,不是“感觉不到”的开销
你只用id和username,但SELECT *会把profile_json、avatar_blob甚至backup_log TEXT一并拖出来。InnoDB对溢出字段单独存页,读一行要三次随机I/O:索引页 → 聚簇页 → 溢出页。同机部署也绕不开loopback TCP栈,net_buffer_length默认16KB只是单次发送上限,不改变总传输量。
ORM场景更危险:
-
MyBatis的resultMap若没显式映射,新增NOT NULL列且无默认值,直接抛SQLException -
Hibernate的findAll()为每列生成对象属性,1000行×10KB = 10MB堆内存白占 - 分页
LIMIT 10000, 20时,MySQL仍要构造并丢弃前10000行完整记录
ALTER TABLE后旧代码立刻错位,不是Bug而是设计缺陷
表结构变更后,SELECT *返回的列顺序、数量、类型全由DDL时刻决定,应用层却还按老逻辑处理。最典型的是JDBC用ResultSet.getObject(2)按位置取值——加一列或MODIFY COLUMN created_at TIMESTAMP FIRST后,getObject(2)突然拿到时间戳而非用户名,错位无声发生。
其他隐性风险:
- 视图用
SELECT *创建,后续ALTER TABLE ADD COLUMN不会自动更新视图定义 - 中间件如
ShardingSphere解析元数据时,*返回列序依赖物理存储顺序,极易错位 - DBA只开了
SELECT(name, phone)权限,SELECT *直接越权读出password_hash
唯一可接受的SELECT *场景只有三类
临时调试、极小配置表、EXISTS子查询——其余全是高危惯性动作。真正难的不是多敲几个字段名,而是想清楚:这一查到底要什么数据、谁在消费、字段生命周期多长、下次加列会不会崩。工程上没有“省事”,只有“暂时没暴露的问题”。











