为什么MySQL不建议使用SELECT *:性能陷阱与优化解析

山海之间
山海之间
发布时间:2026-10-13 | 4人浏览过
原创

虽然SELECT *在开发初期能简化代码,但其对数据库性能的负面影响不容忽视。本文从磁盘I/O、网络传输、索引失效及连接查询四个维度,深入解析为何在生产环境中应避免使用该语法,并提供相应的优化建议。

“不要使用SELECT *”几乎已成为MySQL使用的一条金科玉律,《阿里Java开发手册》也明确禁止将其作为查询字段列表。尽管它因开发效率高且无需随字段变更修改SQL而受欢迎,但盲目使用会带来显著的性能隐患。

1. 不必要的磁盘I/O开销

MySQL将用户记录存储在磁盘上,查询操作本质上是一次磁盘I/O行为(假设数据未缓存)。当使用SELECT *时,若表中包含TEXT、MEDIUMTEXT或BLOB等大字段,即使业务逻辑并不需要这些数据,MySQL也会读取它们,造成极大的资源浪费。

第1步:理解内存管理机制

许多人误以为SELECT *会占据大量服务器内存,但实际上MySQL Server层采用流式处理模式:

  • 每从存储引擎获取一行,即写入内存缓冲区net_buffer(默认大小由net_buffer_length控制,通常为16KB)。
  • 当net_buffer写满后,再将数据通过本地网络栈的socket send buffer发送给客户端。
  • 客户端读取完成后清空net_buffer,继续下一行。

因此,结果集占用的临时内存最大不超过net_buffer_length,不会因为多查询几个字段而无限增长内存占用。但这并不能掩盖其带来的磁盘读取和网络传输代价。

2. 加重网络时延

承接上一节,虽然单次传输的数据量受net_buffer限制,但如果查询了大量大文本或二进制字段,总数据体积依然巨大。这直接导致TCP通信包的数量增加,进而推高网络延迟。

若MySQL服务器与应用服务器分离,这种开销尤为明显;即便在同一台机器上,TCP协议自身的握手与确认机制也会带来额外的时间成本。

3. 无法使用覆盖索引(Leading to 回表)

这是SELECT *最致命的性能陷阱之一。为了说明这一点,我们先创建一个示例表:

CREATE TABLE `user_innodb` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `gender` tinyint(1) DEFAULT NULL,
  `phone` varchar(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `IDX_NAME_PHONE` (`name`,`phone`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

InnoDB会自动为id主键创建聚簇索引(Clustered Index),其叶子节点包含完整的用户记录,结构如下所示:

当我们执行以下查询:

SELECT * FROM user_innodb WHERE name = '蝉沐风';

查看执行计划(见下图)可知,该语句使用了二级索引IDX_NAME_PHONE。二级索引的叶子节点仅包含索引列(name, phone)和主键id:

由于SELECT *要求返回所有字段,InnoDB必须拿着找到的主键id,回到聚簇索引中去查找完整的一行数据,这个过程称为回表(Bookmark Lookup)。

如何避免回表?使用覆盖索引。如果我们只查询索引中包含的字段:

SELECT id, name, phone FROM user_innodb WHERE name = "蝉沐风";

执行计划显示Extra列为Using index,表示查询列完全被索引覆盖,无需回表即可获取结果,效率大幅提升:

4. 拖慢JOIN连接查询效率

在多表连接时,SELECT *会进一步恶化性能。我们创建两张表进行演示:

CREATE TABLE `t1` (
  `id` int NOT NULL,
  `m` int DEFAULT NULL,
  `n` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT;

CREATE TABLE `t2` (
  `id` int NOT NULL,
  `m` int DEFAULT NULL,
  `n` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT;

若执行强制驱动表的连接查询:

SELECT * FROM t1 STRAIGHT_JOIN t2 ON t1.m = t2.m;

在没有索引辅助的情况下,MySQL会使用嵌套循环连接(NLJ),这会反复扫描被驱动表。虽然现代MySQL通常会使用更高效的策略,但无论哪种策略,SELECT *都有害:

对于基于内存块的算法(如MySQL 8.0前的BLJ或新的Hash Join),系统会使用一个固定大小的内存区域join buffer来暂存驱动表的部分记录以匹配被驱动表:

关键在于:只有查询列表中的列才会放入join buffer中。如果使用SELECT *,会将不需要的字段也塞入这个有限的缓冲区,导致join buffer更快满载。一旦满员,MySQL就必须分批处理,多次访问被驱动表,从而增加I/O消耗。

执行计划证实了这一点(见下图,展示Hash Join机制):

总结而言,尽量避免SELECT *,显式指定所需的字段,不仅能避开回表开销,还能提高join buffer的利用率,是提升MySQL SQL性能的通用法则。

以上就是关于为什么MySQL不建议使用SELECT *的详细内容,更多关于MySQL查询优化的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表