游乐游手机版
首页/数据库/文章详情

SQL查询为何应指定列名而非使用星号以提升性能

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

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

为什么SQL查询建议指定具体列名而非星号_减少IO开销提高查询性能

直接说结论:SELECT * 不是语法错误,但会隐式放大磁盘IO、网络传输、内存占用和执行计划不确定性——尤其在字段含 TEXTBLOB 或表结构随时间膨胀时,性能退化会突然变得明显。

为什么 SELECT * 会增加磁盘IO

很多人以为数据库是按“行”来读取数据的,其实不然。数据是以“页”为单位从磁盘加载到内存缓冲池的,比如 InnoDB 默认一页就是 16KB。当你写下 SELECT *,数据库引擎可不管你需要哪些字段,它会把这一行里所有的数据,一个字节不落地全部读进来。

这会导致什么情况呢?举个例子:一张文章表里有个 content 字段,类型是 TEXT,单条记录的实际内容可能高达 50KB。但你的业务逻辑可能只需要 idtitle 这两个小字段,加起来不过百来字节。用上星号,你就得为那 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 后面明确列出所需的列名,这不仅仅是一个性能优化的好习惯,更是一份清晰的“接口契约”。它明确告诉后来的开发者:这张表的哪些字段被哪些业务所依赖。一旦表结构需要调整,所有相关的查询语句都必须被同步审视和核对,这本身就是一种保障系统健壮性的工程实践。

来源:https://www.php.cn/faq/2444878.html
上一篇Redis双机部署方案详解 主从与哨兵两种架构实战指南 下一篇MySQL大文本字段索引优化方案全文索引与前缀索引详解
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Redis 7.0增量AOF重写RDB前导码配置详解
数据库 · 2026-07-02

Redis 7.0增量AOF重写RDB前导码配置详解

先说一个几乎所有人都踩过的典型误区:很多人把 aof-use-rdb-preamble yes 当作开启“增量重写”的开关。实际上,这个配置只干了一件事——让重写后的 AOF 文件头部带上 RDB 快照。它解决的是加载速度问题,跟“增量重写”本身的概念压根不是一回事。真正的增量重写,依赖的是 Red

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践
数据库 · 2026-07-02

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践

直接在Tornado里用SQLAlchemy同步执行SQL,结果就是阻塞IOLoop,所谓“异步框架里写同步数据库代码”,等于白搭。安全执行的关键不是“怎么写SQL”,而是“怎么不卡住事件循环”。 为什么不能在RequestHandler里直接调用session execute() 因为sessio

利用SQL触发器实现在INSERT数据时自动同步到审计表
数据库 · 2026-07-02

利用SQL触发器实现在INSERT数据时自动同步到审计表

先说结论:可以用触发器把 INSERT 数据同步到审计表,但必须用 AFTER INSERT,并且审计表的字段顺序、类型、字符集得和源表严格一致。否则,轻则写入错位、数据截断,重则直接报错、丢数据。下面把这些坑一个一个掰开说。 能,但必须用 AFTER INSERT,且审计表字段顺序、类型、字符集要

如何用SQL编写按不同工作日统计员工出勤率
数据库 · 2026-07-02

如何用SQL编写按不同工作日统计员工出勤率

在实际业务中,统计不同工作日的出勤率是HR系统里的高频需求。如果直接按日期函数分组,很容易掉进语言环境、索引失效或分母口径的坑里。下面就来拆解具体的实现要点。 必须用 CASE WHEN 将日期映射为固定 weekday 标签(如 Mon )再分组,避免语言环境导致的分组断裂;需过滤 DOW IN

Spring Boot 3动态拼接SQL为何引发严重安全漏洞
数据库 · 2026-07-02

Spring Boot 3动态拼接SQL为何引发严重安全漏洞

SQL注入漏洞的核心成因,本质上是因为用户输入直接参与了SQL语句的字符串拼接,而未采用参数化绑定机制。在MyBatis中使用${}、QueryWrapper中调用apply()与last()、JPA的@Query注解进行拼接等操作,都会绕过PreparedStatement的安全防护。动态字段必须