SQL 为什么不建议滥用 SELECT *?

7次阅读

select * 会显著降低查询性能并引发多种风险:增加I/O与网络开销、阻碍覆盖索引使用、导致回表、影响执行计划稳定性、妨碍分区裁剪、在表结构变更时易出错,仅适用于临时排障、调试或极小表。

SQL 为什么不建议滥用 SELECT *?

SELECT * 会拖慢查询速度

数据库要读取整行所有字段,哪怕你只用其中一两个。磁盘 I/O 和网络传输量都变大,尤其表里有 TEXTBLOB 或长字符串字段时,性能下降更明显。如果加了索引覆盖优化,SELECT * 还会让优化器放弃使用覆盖索引,转而回表查数据。

  • 索引只包含部分列,但 SELECT * 要求所有列,mysql/postgresql 只能先走索引找主键,再回聚簇索引捞全行
  • 宽表(比如 50 列)+ 高并发查询,内存和网卡容易成瓶颈
  • ORM 自动生成 SELECT *(如 DjangoModel.objects.all())时,要特别留意实际需要哪些字段

SELECT * 在表结构变更时容易出错

字段顺序或类型变化后,应用层按位置取值的代码(比如 JDBC 的 ResultSet.getObject(2))会直接错位;用 ORM 映射时也可能因新增 NOT NULL 字段没设默认值而抛异常。

  • 添加新列:旧版应用若依赖列序,SELECT * 返回多一列,下标越界或类型不匹配
  • 删除/重命名列:查询仍成功,但应用拿到 null 或空字符串,逻辑出错且难定位
  • 修改字段类型(如 VARCHAR(255)TEXT):某些驱动可能截断或报转换错误

SELECT * 影响查询计划稳定性

优化器基于统计信息估算成本,而 * 让它无法预判实际需要哪些列,导致执行计划波动。例如 PostgreSQL 的 pg_stat_statements 会把不同字段列表的语句视为不同查询,但 SELECT * 看似统一,实则每次解析都要重新推导列集合,缓存命中率低。

  • 同一张表,SELECT a,b FROM tSELECT a,c FROM t 可能复用同一个索引;但 SELECT * 强制走全表扫描或更大范围索引
  • 分区表中,SELECT * 可能阻止分区裁剪(Partition Pruning),扫到本不该访问的分区
  • 物化视图或查询重写规则(如 MySQL 的 Query Rewrite Plugin)对 * 支持有限,容易绕过优化

什么时候可以勉强用 SELECT *

仅限于临时排障、开发调试、或明确知道表极小(SELECT * —— 因为视图一旦创建,底层表改了,视图不会自动更新字段列表。

  • MySQL 视图创建时会固化列定义,但用 SELECT * 创建的视图,后续 ALTER TABLE 加字段不会反映在视图里
  • SELECT * 写迁移脚本?小心目标库字段顺序不同,INSERT ... SELECT * 插入错列
  • DBA 做巡检时敲 SELECT * FROM pg_stat_activity LIMIT 5 没问题,但写进监控脚本就得固定字段

真正麻烦的不是语法本身,而是它掩盖了数据访问意图——你到底要什么字段、为什么需要、谁在用、会不会变。漏掉一个 created_at 可能让业务日志少关键时间戳,多查一个 user_avatar 可能让接口慢 200ms。

text=ZqhQzanResources