先抛一个核心观点:SELECT * 在语法上完全正确,但它就像数据库里一个隐形的“性能放大器”。平时风平浪静,一旦遇到大字段、表结构膨胀或者流量高峰,它就会把磁盘IO、网络传输、内存消耗乃至执行计划的不确定性,成倍地暴露出来。

直接说结论:SELECT * 不是语法错误,但会隐式放大磁盘IO、网络传输、内存占用和执行计划不确定性——尤其在字段含 TEXT、BLOB 或表结构随时间膨胀时,性能退化会突然变得明显。
为什么 SELECT * 会增加磁盘IO
很多人以为数据库是按“行”来读取数据的,其实不然。数据是以“页”为单位从磁盘加载到内存缓冲池的,比如 InnoDB 默认一页就是 16KB。当你写下 SELECT *,数据库引擎可不管你需要哪些字段,它会把这一行里所有的数据,一个字节不落地全部读进来。
这会导致什么情况呢?举个例子:一张文章表里有个 content 字段,类型是 TEXT,单条记录的实际内容可能高达 50KB。但你的业务逻辑可能只需要 id 和 title 这两个小字段,加起来不过百来字节。用上星号,你就得为那 50KB 的文本内容支付额外的 IO 成本。如果表设计上没做好垂直拆分,让这种大字段和高频查询的小字段混在一起,SELECT * 还会直接扼杀使用覆盖索引的机会,让查询不得不回表,性能雪上加霜。
怎么应对?首先,养成习惯,用 SHOW CREATE TABLE 命令看清楚表里每个字段的类型和长度,对那些超过 1KB 的字段保持警惕。其次,监控时要特别留意 Innodb_buffer_pool_read_requests(逻辑读请求)和 Innodb_buffer_pool_reads(物理读次数)的比值。如果这个比值突然大幅下降,很可能就是 SELECT * 引发了大量的物理磁盘读取。
网络传输和应用层内存浪费更隐蔽
磁盘IO的代价看得见,但网络和内存的消耗往往更隐蔽。MySQL 协议会把结果集序列化后通过 TCP 连接发送给客户端。字段越多、越长,单次响应的数据包就越大,这不仅拖慢了用户感知的查询延迟,还可能挤占宝贵的网络带宽。
在应用层,问题同样存在。比如 Ja va 应用常用 ORM 框架将结果集全量映射到实体对象,即使用户只想要一个 id,JVM 仍然需要为所有字段分配对象引用和临时的字节数组。在微服务架构下,如果服务间通过 JSON 传递 SELECT * 的查询结果,一个几 MB 的 content 字段很容易导致单次响应超限,触发网关的流控或超时机制。
这里有几个关键参数需要注意:MySQL 的 max_allowed_packet 参数默认只有 4MB,大字段配合星号查询很容易触发 “Packet too large” 错误。而在 PostgreSQL 中,如果排序或聚合操作已经吃紧了 work_mem 配置的内存,SELECT * 带来的额外数据量会更快地耗尽内存,迫使数据库将中间结果写入磁盘,严重拖慢速度。
执行计划不稳定:优化器“猜不透”你要什么
数据库优化器的工作是选择最高效的执行路径,它严重依赖统计信息和查询语句本身提供的线索。SELECT * 就像给优化器蒙上了一层眼罩——它无法判断查询是否能够仅通过索引就获取全部所需数据(即使用覆盖索引),这常常导致本可避免的“回表”操作或全表扫描。
一个典型的错误现象是:你在 name 字段上建立了索引,用 EXPLAIN 查看 SELECT id, name FROM t 时,Extra 列显示 Using index,说明走了覆盖索引,效率很高。但一旦改成 SELECT *,Extra 列可能就变成了 Using index condition 或者干脆没有索引提示,这意味着引擎必须回表去取其他字段的数据。更令人头疼的是,同一张表,SELECT id, name 可能走了状态索引,而 SELECT * 却可能因为优化器估算的回表成本不同,阴差阳错地走了主键扫描,性能表现截然不同。
因此,务必使用更强大的执行计划分析工具,如 MySQL 8.0+ 的 EXPLAIN FORMAT=TREE 或 PostgreSQL 的 EXPLAIN (ANALYZE),来验证查询的真实执行路径。在设计组合索引时,要有前瞻性:把 SELECT 子句中高频出现的字段,按顺序放在索引的后面。例如,如果业务常查 SELECT user_id, created_at, status,那么建立 INDEX idx_user_status (status, user_id, created_at) 这样的索引,就能让这个查询完美地利用覆盖索引。
最后,必须警惕一点:优化 SELECT * 不是为了解决“现在慢”的问题,而是为了预防“将来崩”的风险。随着表结构增加字段、数据库版本升级、业务流量增长,星号查询带来的性能隐患会第一个爆发。在 SELECT 后面明确列出所需的列名,这不仅仅是一个性能优化的好习惯,更是一份清晰的“接口契约”。它明确告诉后来的开发者:这张表的哪些字段被哪些业务所依赖。一旦表结构需要调整,所有相关的查询语句都必须被同步审视和核对,这本身就是一种保障系统健壮性的工程实践。
