首页/数据/SQL 速查/避免 SELECT *
性能与执行计划72 条

避免 SELECT *

需要什么列就写什么列:减少 IO 与网络传输,保住覆盖索引机会,也让代码对表结构变更更稳健。

SQL 速查数据更新于 2026-10-13

为什么别写星号

用星号取全部列有三个代价:不需要的列也会被读出来并经过网络传输,宽表上这些列里往往还有大文本字段;查询容易被排除在覆盖索引之外,本来只读索引就能完成的操作被迫回表;返回的列集合随表结构变化,代码里按位置解析的逻辑会在加列后错位。写清楚列名多花几十秒,换来的是稳定且可控的读取行为。

基本信息

适用平台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;

代价对照

维度星号取全部列显式列清单
读取成本不需要的列也要读只读需要的列
网络传输宽表上体积明显增大按需传输
覆盖索引多数情况被迫回表有机会只用索引完成
结构变化加列后返回集合改变不受影响
大字段文本与二进制列一起被读出可完全避开
覆盖索引的收益常常被低估:把过滤列与主键一起写进列清单,很多查询可以完全不读数据行。判断是否命中可以在计划里看额外信息,出现「只用索引」就是理想结果。
有两处场景可以放宽:临时排查线上数据时用星号更省事,明确知道表很窄且查询频率低时影响也有限。但这两类场景都不该出现在正式的业务代码里。

易错点

  • 在表结构可能变化的查询里用星号,返回集合与代码假设不一致,序列化直接错位
  • 按位置复制整表数据,源表加列后数据写错列,属于会造成数据错乱的操作
  • 以为星号只是写法问题:大字段被一起读出时网络与内存开销成倍增加
  • 漏掉主键列导致无法只读索引完成查询,把本该轻量的查询变成回表扫描
  • 只在开发环境看性能:数据量小看不出差别,到生产宽表上才暴露

数据来源与参考

  • MySQL 8.0 Reference Manual · Optimizing SELECT Statements
  • PostgreSQL Documentation §14.1 Using EXPLAIN
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌