<p>SELECT * 会强制回表并引发多重性能问题:放弃覆盖索引、触发磁盘随机I/O、增加CPU解析开销、放大网络与内存压力,且掩盖字段变更风险。</p>

SELECT * 强制回表,让索引形同虚设
哪怕你 WHERE 条件命中了索引,SELECT * 也会让 MySQL 放弃「覆盖索引」,转而回聚簇索引取全行数据。比如有联合索引 (status, id, name),执行 SELECT id, name FROM user WHERE status = 1 时,EXPLAIN 的 Extra 字段会显示 Using index;换成 SELECT *,就变成 Using where 或空值,意味着必须多走一次 B+ 树查找——从辅助索引页跳到主键页读剩余字段。
这不只是“多查一点”,而是把内存级的索引扫描,硬拖成磁盘级的随机 I/O。尤其当 buffer pool 不够大、热点数据不在内存时,每行都可能触发一次物理读。
- 辅助索引页小、常驻内存;聚簇索引页大、易落盘
- 宽表(>10 列)+ 大字段(
TEXT、JSON)会让单行跨多个 16KB 页,I/O 次数指数上升 -
EXPLAIN中key_len明显小于索引定义长度,就是回表信号
网络和解析开销被严重低估
很多人只盯着磁盘 I/O,却忽略 Server 层的隐性成本:SELECT * 让 MySQL 必须逐字段解包、类型校验、字符集转换、再序列化为协议包。字段越多、越宽(尤其 utf8mb4 + emoji)、含 BLOB,CPU 消耗越不可控。
实测对比:同样 WHERE status = 1,返回 5 列比 20 列的 CPU 占用高近 3 倍;若其中含一个 content TEXT,单次查询序列化耗时翻倍。
- 应用层没用的字段(如
created_at、version)白跑一遍解析+传输 - ORM(如 MyBatis)还会额外做反射赋值、空值判断,放大延迟
- 结果集超
net_buffer_length(默认 1MB)时,MySQL 分包发送,TCP 小包问题加剧 RTT
EXPLAIN 里这几个字段才是真瓶颈
别只看 rows,它只是预估扫描行数。真正暴露 SELECT * 代价的是这几项:
-
type是ALL或index:基本等于全表/全索引扫描,SELECT *极易触发 -
key为空:没走索引;非空但rows远大于实际返回数,说明索引选择性差或被浪费 -
Extra出现Using filesort或Using temporary:字段多导致内存临时表膨胀,排序/分组被迫在 Server 层做 -
Extra是Using where; Using index才是理想状态——覆盖索引 + 条件过滤全在引擎层完成
不是不能用,而是得清楚代价在哪
开发环境快速看表结构、脚本临时导出全量数据,SELECT * 确实省事。但只要涉及线上查询、接口响应、高并发或大表(行数 > 10 万),就必须明确列名。
最容易被忽略的一点:字段变更后,SELECT * 不会报错,但可能悄悄拉回新字段(比如加了个 log LONGTEXT),瞬间拖垮接口。而显式列出字段,既限定了传输边界,也倒逼你思考「这一行里,到底哪些字段真被用到了」。


















