news 2026/5/12 0:02:55

<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/5/11 15:36:48

大模型落地必看:如何用量化指标,给你的模型模型打个分?

大家好!我是你们的AI技术老友。 很多同学在后台私信我:“博主,我熬夜用显卡跑完了模型模型,结果感觉回复还是‘差点意思’,但是‘意思’到底差在哪?我该怎么跟增压报告音响效果?” 确实&#…

作者头像 李华
网站建设 2026/5/10 19:35:36

真的太省时间了!一键生成论文工具 千笔·专业论文写作工具 VS 万方智搜AI

随着人工智能技术的迅猛发展,AI辅助写作工具逐渐成为高校学生完成毕业论文的重要助手。越来越多的学生开始借助这些工具提升写作效率、降低撰写难度。然而,在琳琅满目的AI工具中,许多学生却陷入了“选择困难”的困境——既担心工具的专业性不…

作者头像 李华
网站建设 2026/4/27 14:09:33

医院OA系统集成百度UMEDITOR后,如何高效处理PDF文献中的图片转存?

2023年XX月XX日 | 企业级编辑器插件选型与开发日志 一、需求背景与市场调研 1.1 核心需求痛点 政务项目特殊性:需100%兼容信创环境(麒麟/UOS龙芯/鲲鹏)IE8兼容:部分政务系统仍运行在Windows XPIE8环境富文本保真:需支…

作者头像 李华
网站建设 2026/5/11 22:13:26

【30天精通汇编】Day 2: CPU架构与寄存器

【30天精通汇编】Day 2: CPU架构与寄存器📅 学习时间:4-5小时 🎯 学习目标:理解CPU工作原理,掌握x86寄存器 💡 难度:★★☆☆☆📝 Day 1 练习题答案 练习1: - 10110(二进…

作者头像 李华