SELECT *会让InnoDB多读一次磁盘,因其需先通过辅助索引定位主键,再回表查询聚簇索引获取整行数据,触发额外B+树查找;若含TEXT等大字段,还可能引发溢出页读取,IO次数增至三次。

SELECT * 为什么会让 InnoDB 多读一次磁盘
InnoDB 读数据不是按行,而是按页(默认 16KB)从磁盘载入 buffer pool。SELECT * 要求引擎把整行所有字段都加载进来,哪怕你只用 id 和 status。如果表里有 content TEXT,单条记录物理存储占 40KB,那每查一行就要拉至少 3 个页——而 SELECT id, status 可能仅需 1 个页就能覆盖全部数据。
常见错误现象:
- 监控看到
Innodb_buffer_pool_reads突增,但Innodb_buffer_pool_read_requests没明显变化 → 物理读暴增,大概率是SELECT *拉了大字段 - 执行
EXPLAIN显示type=ref,但Extra从Using index变成Using where; Using index; Using filesort→ 覆盖索引失效,被迫回表
覆盖索引直接失效的典型场景
假设你建了联合索引 INDEX idx_order_user_status ON orders(user_id, status),以下两句话行为完全不同:
SELECT user_id, status FROM orders WHERE user_id = 123;
→ 走 idx_order_user_status,Extra = Using index,纯索引扫描,不碰聚簇索引
SELECT * FROM orders WHERE user_id = 123;
→ 优化器发现索引里没有 created_at、amount 等列,只能先用索引定位主键,再回聚簇索引捞全行 → 多一次 B+ 树搜索,I/O 翻倍
实操建议:
- 用
SHOW CREATE TABLE orders看清哪些字段是TEXT、BLOB或长度 >1KB 的VARCHAR - 对高频查询路径,用
EXPLAIN FORMAT=JSON查used_columns字段,确认是否真用上了覆盖索引 - 别信“这个表就十几列,* 无所谓”——只要有一列是
TEXT,它就可能让整页失效
应用层和网络链路里的隐性开销
SELECT * 不只是数据库慢,它在传输和解析阶段也埋雷:
- MySQL 协议序列化后走 TCP,字段越多、越长,单次响应 payload 越大;微服务间 JSON 透传时,
content字段一加,响应轻易超 2MB,触发网关限流 - Java 用
ResultSet.getObject(2)按下标取值?表新增一列,getObject(2)就指向了错字段,静默出错 - ORM 如 MyBatis 的
resultMap若没显式映射,SELECT *返回字段顺序一变,对象属性就错位;JPA 的@Entity若新增NOT NULL字段且无默认值,启动直接报SQLException -
max_allowed_packet默认 4MB,SELECT *+ 宽表 + 大字段,容易触发Packets larger than max_allowed_packet are not allowed
哪些地方改了也没用:伪优化陷阱
不是删掉 *、补上字段名就安全了。真实翻车点往往藏在细节里:
- 视图或 CTE 里还写着
SELECT *,外层改了字段列表,内层照样全拉 → 整体 IO 没降 -
ORDER BY created_at但没把created_at写进SELECT列表 → MySQL 5.7+ 会报错,8.0 默认允许但执行计划可能劣化 - JOIN 多表时两个
id都选了,没加别名 → 结果集里只有一个id,另一个被覆盖,应用取不到 - 字段值为
NULL,但业务逻辑默认当空字符串,改写后没加COALESCE(phone, '')→ 应用层 NPE
最麻烦的不是性能数字,是你没法一眼看出哪几个字段真被业务用了——SELECT * 掩盖了数据访问意图。一旦表结构动了,问题不是“快不快”,而是“对不对”。


















