应避免使用select *,因其导致全表扫描、索引失效、io与网络开销增大、缓存效率降低及隐式耦合风险;改用明确字段可启用覆盖索引、减少回表、压缩join结果、提升orm稳定性。

直接查所有字段,多数时候是性能浪费的起点。不加筛选地用 SELECT *,不仅拖慢查询,还会让索引失效、增加网络和内存开销,甚至埋下表结构变更时的兼容隐患。
为什么 SELECT * 会拖慢查询
数据库要返回所有列,意味着:必须读取每行全部字段(哪怕你只关心其中 2 个);如果表里有 TEXT、BLOB 或长 VARCHAR 字段,IO 和传输成本会陡增;更关键的是,它无法利用「覆盖索引」——即仅靠索引本身就能返回结果,无需回表查数据行。
- 全表扫描概率上升:优化器发现无法用索引覆盖全部输出列,可能放弃走索引
- 网络带宽浪费:客户端收到大量无用字段,尤其在高并发或移动端场景下明显
- 缓存效率下降:查询结果集变大,MySQL 查询缓存(若启用)或应用层缓存命中率降低
- 隐式耦合风险:表新增一列,
SELECT *结果结构就变了,下游代码可能崩溃
改成指定列后,哪些地方真正受益
把 SELECT * 换成明确列出需要的字段,不只是“写法更规范”,而是触发了底层执行路径的实质性变化。
- 能走覆盖索引:比如
CREATE INDEX idx_user_id_name ON user(id, name),那么SELECT id, name FROM user WHERE id = 123完全不用访问数据页 - 减少 InnoDB 聚簇索引访问:少读几列 = 少解析几段记录 = 更少的 CPU 和 buffer pool 压力
- JOIN 场景更可控:多表关联时,
SELECT *会让中间结果集膨胀数倍,而指定列可大幅压缩临时结果体积 - ORM 映射更稳定:如 MyBatis、SQLAlchemy 等框架对字段名敏感,显式声明避免列顺序错位或类型推断错误
SELECT * 改写时容易踩的坑
不是简单删掉星号、补上字段名就万事大吉。实际改写中几个高频翻车点:
- 忽略
NULL字段:比如原表某列为phone VARCHAR(20) NULL,但业务逻辑默认当空字符串处理,改写后没加COALESCE(phone, ''),导致应用层 NPE - 漏掉排序/分组依赖字段:例如
ORDER BY created_at但没把created_at写进SELECT列表(某些 MySQL 版本会报错或行为异常) - 视图或子查询里仍用
*:外层改了,内层没动,整体收益归零 - 硬编码字段名却没同步更新注释或文档:后续维护者误以为字段已废弃
- JOIN 多表时字段名冲突:比如
user.id和order.id都选了,没加别名,结果被覆盖或报错
什么时候可以暂不改 SELECT *
极少数真实场景下,保留 SELECT * 是合理选择,但需有明确理由:
- 调试阶段快速探查数据:用完立刻删,不进 Git 历史
- 表只有 2–3 个短字段且无大对象,且查询频次极低(如后台管理页的单条详情)
- 使用 ORM 的「懒加载」机制,且明确配置了只 fetch 关联对象 ID(此时 * 实际不触发关联字段加载)
- ETL 全量导出场景,目标就是整行,且传输链路已压缩(如用 Parquet + Snappy)
但只要涉及线上高频查询、分页、JOIN 或任何不确定字段需求的接口,SELECT * 就不该出现——这不是“够用就行”,而是“多扫一行都是浪费”。











