select *必然破坏覆盖索引,因其要求所有字段而索引几乎总不包含全部列,导致必须回表引发随机i/o、放大网络传输与cpu解析开销。

SELECT * 不会直接让索引失效,但会让 MySQL 无法走覆盖索引,进而触发回表、放大 I/O、拖慢查询——这是性能退化的根本原因。
为什么 SELECT * 必然破坏覆盖索引
覆盖索引要求「查询所需的所有字段」都包含在索引中。而 SELECT * 意味着要取整行数据,只要索引不包含所有列(几乎总是如此),就必然要回聚簇索引取剩余字段。
- 例如:表
user(id, name, email, content),有联合索引(status, id, name) -
SELECT id, name FROM user WHERE status = 1→EXPLAIN显示Extra: Using index,纯索引扫描 -
SELECT * FROM user WHERE status = 1→Extra变为空或Using where,必须回表查email和content - 回表是随机 I/O,比顺序扫描更慢;尤其当
content是TEXT类型时,可能跨页读取,I/O 次数翻倍
SELECT * 如何放大网络与解析开销
MySQL Server 层拿到引擎返回的原始字节后,要逐字段解包、类型转换、字符集校验、序列化成协议包。字段越多,这部分 CPU 开销越明显。
- 大字段(如
TEXT、BLOB)超过 728 字节时,InnoDB 会额外存储到溢出页,每次读取多一次 I/O - ORM(如 MyBatis)会为每个字段做反射赋值、空值判断、JSON 序列化,
*白跑这些逻辑 - 结果集超过
net_buffer_length(默认 1MB)时,MySQL 分多次发包,TCP 小包增多,RTT 上升 - 哪怕 DB 和应用同机,仍走 TCP 协议栈,传输延迟不可忽略
哪些 EXPLAIN 信号说明 SELECT * 已经变重
别只看 rows,这几个字段才是真实瓶颈指示器:
-
type是ALL或index:全表扫或全索引扫,SELECT *很难避免 -
key_len明显偏小(比如联合索引长 100 字节,却只用前 5 字节):说明索引没被充分利用,仍需回表 -
Extra出现Using filesort或Using temporary:字段越多,内存临时表膨胀越快 -
rows和实际返回行数差距极大(如rows=10000但LIMIT 20):MySQL 扫了 1 万行才凑够 20 条,*让每行成本更高
什么时候真不能避免 SELECT *?
极少场景下可接受,但必须清楚代价:
- 初学调试:仅限本地开发环境,且表极小(
- ETL 全量导出:明确知道下游需要全部字段,且走离线通道(如 mysqldump + 压缩文件),不走应用层网络
- 某些 ORM 的懒加载触发逻辑:但应确保后续不会高频触发 N+1 查询
生产环境里,连 created_at、updated_at 这类字段都该按需取——它们常被忽略,却白占 I/O 和解析资源。











