为什么别写星号
用星号取全部列有三个代价:不需要的列也会被读出来并经过网络传输,宽表上这些列里往往还有大文本字段;查询容易被排除在覆盖索引之外,本来只读索引就能完成的操作被迫回表;返回的列集合随表结构变化,代码里按位置解析的逻辑会在加列后错位。写清楚列名多花几十秒,换来的是稳定且可控的读取行为。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL 12 及以上,通用建议 |
|---|---|
| 输入输出成本 | 不需要的列同样产生读取与传输开销 |
| 覆盖索引 | 列清单越精确,越有机会只读索引完成查询 |
| 稳定性 | 显式列清单不受表加列影响,按位置解析不会错位 |
| 例外情况 | 临时排查、交互式查询可以放宽要求 |
典型使用场景
- 接口查询:只取前端真正需要的列,减少响应体积
- 宽表读取:表里有大文本或二进制字段时收益最明显
- 列表页分页:配合复合索引做到只读索引不读数据行
- 导出任务:大字段会显著放大导出文件与耗时
- 代码评审:把使用星号的查询列为待改进项
改写与验证
改写与验证
-- 精确列清单:有机会只读索引就完成
EXPLAIN SELECT id, user_id, created_at
FROM orders
WHERE user_id = 100;
-- 额外信息里出现「只用索引」即表示不需要回表
-- 取全部列:多出的列迫使回表读取数据行
EXPLAIN SELECT * FROM orders WHERE user_id = 100;
-- 更危险的用法:按位置复制整表,加列即错位
-- INSERT INTO orders_bak (SELECT * FROM orders);
-- 正确做法:两侧都写清楚列清单
INSERT INTO orders_bak (id, user_id, created_at)
SELECT id, user_id, created_at FROM orders;代价对照
| 维度 | 星号取全部列 | 显式列清单 |
|---|---|---|
| 读取成本 | 不需要的列也要读 | 只读需要的列 |
| 网络传输 | 宽表上体积明显增大 | 按需传输 |
| 覆盖索引 | 多数情况被迫回表 | 有机会只用索引完成 |
| 结构变化 | 加列后返回集合改变 | 不受影响 |
| 大字段 | 文本与二进制列一起被读出 | 可完全避开 |
覆盖索引的收益常常被低估:把过滤列与主键一起写进列清单,很多查询可以完全不读数据行。判断是否命中可以在计划里看额外信息,出现「只用索引」就是理想结果。
有两处场景可以放宽:临时排查线上数据时用星号更省事,明确知道表很窄且查询频率低时影响也有限。但这两类场景都不该出现在正式的业务代码里。