news 2026/4/3 4:47:10

<span class=“js_title_inner“>MySQL 性能优化第一课:为什么说 `SELECT *` 扼杀了“覆盖索引”?</span>

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
<span class=“js_title_inner“>MySQL 性能优化第一课:为什么说 `SELECT *` 扼杀了“覆盖索引”?</span>
关注我们,设为星标,每天7:30不见不散,每日java干货分享

场景还原:
周五下午,线上系统突然报警,接口响应时间从 20ms 飙升到 2s。
DBA 排查发现,数据库网卡流量被打满,且 Buffer Pool 命中率急剧下降。

原因:有个新发布的这个功能,查询用户详情时用了SELECT *。原本表里只有几个字段,前天为了存用户头像,新加了一个avatar_base64的大字段(TEXT)。
结果,每次查询都把这个巨大的字符串拖出来,不仅撑爆了内存,还堵死了网络。


1. 核心影响一:扼杀“覆盖索引” (The Covering Index Killer)

这是SELECT *最致命的性能影响,却鲜有人知。

原理:
假设你有一个用户表users,建立了一个联合索引idx_name_age (name, age)

  • 场景:只需要查用户的年龄。

写法 A (恶习):

SELECT*FROM users WHERE name ='Alice';

过程:MySQL 先去idx_name_age索引树找到 'Alice',拿到主键 ID。然后,它必须拿着 ID 去“聚簇索引”(主数据文件)里把整行数据读出来(称为“回表”),因为你需要*(包括 address, email 等不在索引里的字段)。

写法 B (最佳):

SELECT age FROM users WHERE name ='Alice';

过程:MySQL 去idx_name_age索引树找到 'Alice',发现你只要age,而age就在这个索引树上。直接返回!不需要回表,不需要读主数据文件。

结论:SELECT *强制数据库进行“回表”操作,导致大量的随机磁盘 I/O,让原本可以毫秒级完成的“覆盖索引”查询失效。


2. 核心影响二:带宽与内存的无底洞

场景:
你的表里包含TEXT(文章内容),BLOB(图片), 或者超长的VARCHAR

  • DB 内存 (Buffer Pool):数据库会将读取的数据页缓存到内存。如果你总是SELECT *读出大字段,这些冷数据会迅速挤掉热点数据,导致缓存命中率暴跌。

  • 网络带宽:假设一行数据 1KB,并发 1000 QPS,就是 1MB/s。如果一行数据因为大字段变成了 10KB,流量瞬间变成 10MB/s,网卡直接报警。

  • 应用层开销:Java/Go 程序接收到这些无用的大数据后,需要进行反序列化(Unmarshalling),消耗大量的 CPU 和 GC 资源。

实战教训:
很多系统变慢,不是因为 CPU 算不过来,而是因为**“对象太大了”**,GC 停顿(Stop-the-World)太频繁。


3. 核心影响三:架构耦合与代码脆弱性

SELECT *会让你的代码与数据库 Schema强耦合

**场景一:INSERT INTO ... SELECT ***

-- 这里的 * 非常危险 INSERT INTO table_archive SELECT*FROM table_current;

如果某天你在table_current加了一个字段,但忘了给table_archive加。这条 SQL 会直接报错,导致归档任务失败。

场景二:字段顺序依赖
有些老旧的代码(或 CSV 导出逻辑)依赖列的顺序。
String name = resultSet.getString(2); // 假设第二列是 name
如果你在表中第一列前面加了个新字段,SELECT *返回的列顺序变了,代码逻辑全乱,张三变成了“1001”。

场景三:多表 JOIN 的命名冲突

SELECT*FROM orders JOIN users ON orders.user_id = users.id;

orders表有create_timeusers表也有create_time
当你SELECT *时,结果集中会出现两个create_time。应用层(如 MyBatis 或 Map)在映射时,很可能出现“后来居上”覆盖的情况,导致你明明想取订单时间,却取到了用户注册时间。


4. 核心影响四:安全隐患 (Data Exposure)

这是最容易被忽视的。

场景:用户表users包含password_hash,salt,mobile,id_card

前端需求:“给我展示用户昵称”。
后端偷懒:return dao.selectUserByWait(id); // 执行了 SELECT *
结果:后端把包含密码哈希、身份证号的整个 User 对象序列化成 JSON 扔给了前端。

虽然前端页面上没展示,但黑客只要按 F12 看一下 Network 请求,所有敏感信息一览无余。这是极严重的数据泄露。


5. 什么时候可以使用 SELECT * ?

当然,SELECT *不是绝对禁忌,以下场景可以适度使用:

  1. 1.临时运维查询:你手动在 Navicat / 命令行里查问题,为了看全貌,当然用*

  2. 2.应用层缓存预热:确实需要把整行对象存入 Redis,且表结构相对稳定。

  3. 3.COUNT(*):这是一个特例。在 MySQL 中COUNT(*)被专门优化过(忽略 NULL),通常比COUNT(id)还要快或持平,这里的*是可以用的


6. 总结

不要为了省去打几个字段名的功夫,而给系统埋下无数的地雷。

  • 性能上:它破坏覆盖索引,增加 I/O 负担。

  • 架构上:它增加耦合,降低代码健壮性。

  • 安全上:它泄露隐私数据。

口诀:字段按需取,索引能覆盖;大列要避开,流量省一半。

推荐阅读 点击标题可跳转

50个Java代码示例:全面掌握Lambda表达式与Stream API

16 个 Java 代码“痛点”大改造:“一般写法” VS “高级写法”终极对决,看完代码质量飙升!

为什么高级 Java 开发工程师喜爱用策略模式

精选Java代码片段:覆盖10个常见编程场景的更优写法

提升Java代码可靠性:5个异常处理最佳实践

为什么大佬的代码中几乎看不到 if-else,因为他们都用这个...

还在 Service 里疯狂注入其他 Service?你早就该用 Spring 的事件机制了

看完本文有收获?请转发分享给更多人

关注「java干货」加星标,提升java技能

❤️给个「推荐 」,是最大的支持❤️

.cls-1{fill:#001e36;}.cls-2{fill:#31a8ff;}

.cls-1{fill:#001e36;}.cls-2{fill:#31a8ff;}

.cls-1{fill:#001e36;}.cls-2{fill:#31a8ff;}

.cls-1{fill:#001e36;}.cls-2{fill:#31a8ff;}

.cls-1{fill:#001e36;}.cls-2{fill:#31a8ff;}

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/2 3:25:11

AI解码金银历史级暴跌“密码”:沃什提名,是巧合还是另有隐情?

摘要:本文通过运用AI数据挖掘模型对金融市场历史数据与实时舆情数据进行深度剖析,结合美联储主席提名事件、宏观经济指标预期以及市场交易行为等多维度因素,分析黄金白银价格历史级暴跌现象及其背后成因。 上周五黄金白银市场遭遇了一场“惊涛…

作者头像 李华
网站建设 2026/3/28 19:12:04

清单来了:8个AI论文平台深度测评,自考毕业论文+格式规范全攻略

在当前学术写作日益依赖智能化工具的背景下,无论是高校师生还是自考学生,都面临着选题困难、文献检索繁琐、格式规范不熟悉等普遍问题。尤其对于自考群体而言,时间有限且缺乏系统指导,更需要高效、可靠的辅助工具来完成毕业论文的…

作者头像 李华
网站建设 2026/3/11 16:42:02

维普AIGC检测怎么降?2026年维普降AI攻略

维普AIGC检测怎么降?2026年维普降AI攻略 学校用维普查AIGC,怎么降AI率? 维普的AIGC检测和知网不太一样,但降AI的方法是类似的。这篇文章告诉你怎么搞定维普AIGC检测。 维普AIGC检测特点 维普AIGC检测用的是语义分析文本指纹技术…

作者头像 李华