避免 SELECT * 原因

避免 SELECT * 原因

场景对比

先看两个等价的查询:

-- 不推荐
SELECT * FROM orders WHERE user_id = 12345;

-- 推荐
SELECT id, order_no, amount, status FROM orders WHERE user_id = 12345;

功能上完全相同,但性能差异很大。下面来拆解为什么不要用 SELECT *

原因一:浪费网络带宽和内存

网络传输成本

SELECT * 返回表的所有字段。如果一个表有 30 个字段,但业务只需要 3 个,那么 90% 的传输数据是浪费的。

假设:
– 查询返回 1000 行
– 表有 30 个字段,单行 1KB
– 实际需要 3 个字段,单行 200B

SELECT *    → 传输 1000KB / 查询
SELECT 字段 → 传输 200KB / 查询

如果 QPS=1000,每秒节省 800MB 网络传输。

应用层内存

Java 程序读取 ResultSet 需要为每个字段建立对象。多读取的字段会额外消耗应用服务器内存,增加 GC 压力。

原因二:无法使用覆盖索引

覆盖索引是 MySQL 重要的优化手段——查询所需的所有字段都在索引中,不需要回表。

-- 假设有复合索引 idx_user_status (user_id, status)

-- 可以使用覆盖索引:无需回表
SELECT user_id, status FROM orders WHERE user_id = 12345;

-- 无法使用覆盖索引:必须回表读取所有字段
SELECT * FROM orders WHERE user_id = 12345;

覆盖索引避免了一次回表操作,而回表意味着随机的磁盘 I/O(主键索引和数据行是分离的存储结构)。对于大量查询的场景,覆盖索引能显著提升性能。

如何判断:EXPLAIN 输出中,Extra 列为 Using index 说明使用了覆盖索引。

原因三:索引变更隐患

-- 假设查询
SELECT * FROM big_table WHERE ...;

-- DBA 修改了表结构(增加/删除字段)
ALTER TABLE big_table ADD COLUMN new_col VARCHAR(500);
ALTER TABLE big_table DROP COLUMN old_col;

SELECT * 的返回结果集自动变化。如果应用层代码硬编码了字段索引位置(如 resultSet.getString(5)),就可能出现问题。显式指定字段名则不受这种影响。

原因四:不必要的 I/O

MySQL 存储引擎在读取一行数据时,需要将整行数据读入内存。对于 InnoDB,数据以页为单位(16KB)存储。如果字段特别多或包含大字段(TEXT、BLOB),读取大字段会带来额外的 I/O 开销。

即使你不需要大字段,SELECT * 也会强制 InnoDB 去读取这些存储在溢出页(off-page)的大字段数据。

性能测试对比

在一张 20 字段的表上测试(100 万行数据):

查询方式 执行时间 扫描行数 返回数据量 索引覆盖
SELECT * 45ms 5800 ~580KB
SELECT 3 个字段 12ms 5800 ~87KB

差距 3~4 倍,且随着数据量增大差距更明显。

什么时候可以用 SELECT *?

有例外场景:

  1. 一次性临时查询:在 MySQL 命令行或 Navicat 中调试,需要查看所有字段
  2. 行数极少的查询:结果集只有几条记录时的性能差异可以忽略
  3. 数据量极小的配置表:全表只有几百行,怎么查都一样

但在应用程序代码中,SELECT * 几乎永远不是好选择。

最佳实践

-- 明确列出需要的字段
SELECT id, user_id, order_no, amount, status, create_time
FROM orders
WHERE user_id = 12345;

这样做的好处:
1. 显示你的意图 —— 阅读代码的人知道需要哪些字段
2. 更容易优化 —— 可以精确地创建覆盖索引
3. 更稳定 —— 表结构变化不影响查询结果
4. 更高效 —— 减少传输和内存占用

总结

避免 SELECT * 的核心原因可以浓缩为三点:浪费网络带宽放弃覆盖索引优化增加潜在维护风险。在写任何 SQL 时,只返回你真正需要的字段。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容