select * 必然破坏覆盖索引并强制回表,因text/blob等大字段不存于索引中,导致extra从using index变为using where,引发额外随机i/o、buffer pool污染、网络带宽激增及反序列化开销上升。

SELECT * 会直接破坏覆盖索引,让本可避免回表的查询强制走聚簇索引——这不是“可能慢”,而是“必然多一次随机 I/O”。
为什么 SELECT * 让 EXPLAIN 的 Extra 从 Using index 变成 Using where
覆盖索引生效的前提,是查询所需字段全部落在同一个二级索引的叶子节点里。一旦用了 SELECT *,MySQL 就必须拿到主键再去聚簇索引里捞整行,哪怕你只差一个 TEXT 字段没在索引中。
- 索引定义是
INDEX idx_name_age (name, age),SELECT name, age FROM t WHERE name = 'Alice'能走覆盖;但SELECT * FROM t WHERE name = 'Alice'一定回表 -
TEXT、BLOB、超长VARCHAR字段根本不会被任何索引存储——加进复合索引定义也没用,MySQL 会静默忽略 - 执行计划里
type=ref没变,但Extra从Using index消失,就是最直接的信号:覆盖已失效
SELECT * 如何悄悄拖垮 Buffer Pool 和网络带宽
数据库缓存和网络链路不是无限的,* 把冷数据、大字段、无用字段全塞进去,代价是隐性的但很真实。
- 一行含
TEXT字段时大小约 8KB,1000 QPS 就是 8MB/s 网络流量;而只查id, name, created_at可能仅 100B/行 → 总流量压到 100KB/s - Buffer Pool 会被这些大字段占满,把真正高频访问的热点行挤出内存,缓存命中率断崖下跌
- 应用层反序列化开销陡增:Java 的
ResultSet.getObject()或 Go 的sql.Rows.Scan()处理 10KB 对象比处理 100B 对象耗更多 CPU 和 GC 压力
哪些场景下 SELECT * 的危害会被放大
不是所有 * 都一样危险,以下几类操作会让问题指数级恶化:
-
INSERT INTO ... SELECT *:源表结构一变(比如加了个json_detail字段),目标表就可能因字段数不匹配报错Column count doesn't match value count - ORM 自动生成语句(如 Hibernate 的
findAll()):默认查全字段,又常搭配 N+1 查询,叠加回表 + 多次网络往返 - 分页查询
LIMIT 10000,20配合SELECT *:MySQL 得先回表取出前 10020 行再丢弃,I/O 成倍浪费 - 聚合视图或中间表同步:字段膨胀后,下游 ETL 解析失败或超时,排查时才发现源头是上游
*
最易被忽略的一点:索引设计本身无法挽救 SELECT *。哪怕你给所有字段建了联合索引,只要表里存在 TEXT 或 BLOB,这个索引就永远无法覆盖——因为它们压根不在索引结构里。写 SQL 时,字段列表不是偷懒项,是性能契约的第一行。











