避免 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 *?
有例外场景:
- 一次性临时查询:在 MySQL 命令行或 Navicat 中调试,需要查看所有字段
- 行数极少的查询:结果集只有几条记录时的性能差异可以忽略
- 数据量极小的配置表:全表只有几百行,怎么查都一样
但在应用程序代码中,SELECT * 几乎永远不是好选择。
最佳实践
-- 明确列出需要的字段
SELECT id, user_id, order_no, amount, status, create_time
FROM orders
WHERE user_id = 12345;
这样做的好处:
1. 显示你的意图 —— 阅读代码的人知道需要哪些字段
2. 更容易优化 —— 可以精确地创建覆盖索引
3. 更稳定 —— 表结构变化不影响查询结果
4. 更高效 —— 减少传输和内存占用
总结
避免 SELECT * 的核心原因可以浓缩为三点:浪费网络带宽、放弃覆盖索引优化、增加潜在维护风险。在写任何 SQL 时,只返回你真正需要的字段。


暂无评论内容