AI图鉴

AI图鉴首页 › AI 资讯 › 索引建了查询却更慢——优化器选错索引与Index Merge的隐藏代价

索引建了查询却更慢——优化器选错索引与Index Merge的隐藏代价

来源:掘金 · 发布时间:10-23 · 由 AI图鉴每小时自动聚合

建索引是为了加速查询,但有时候索引建了,查询反而更慢。本文拆解优化器选错索引的三种典型场景、Index Merge的代价链路,以及用EXPLAIN ANALYZE和FORCE INDEX定位问题的方法

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

刚做DBA那阵子,我对“加索引”这件事有一种朴素的信仰——查询慢?加索引。再慢?再加一个。

直到有一次,一条查询本来跑200ms,我信心满满地加了个索引,结果变成了2秒。

我当时的第一反应是:索引建错了?查了一下,索引建对了,字段类型也没问题。再跑EXPLAIN,优化器确实用了这个新索引。

用了索引,反而更慢了。

那次之后我才明白,索引不是“建了就行”。优化器对索引的选择,有一套自己的逻辑。这套逻辑在大多数时候是对的,但在特定场景下,它会做出看起来合理、实际很糟糕的决定。

先把这件事里绕不开的几个概念说清楚:

索引选择性——索引列中不同值的数量与总行数的比值。选择性越高(不同值越多),索引过滤效果越好。比如主键的选择性是1,status只有5个值,选择性是0.000005。

回表——通过二级索引找到主键值后,再回到聚簇索引读取完整行数据。每次回表是一次随机I/O。

Index Merge——优化器同时使用多个索引,把各自的结果集做交集(Intersection)或并集(Union),再合并输出。

一、优化器为什么会选错索引

优化器选择索引的依据是代价估算。它估算每个候选索引的I/O代价和CPU代价,选最低的那个。问题在于,这个估算有两个容易出偏差的地方。

偏差一:基数估算不准。

优化器用采样估算索引列的基数(不同值的数量)。如果数据分布倾斜,采样结果可能严重偏离实际。比如status列有5个值,但status='DONE'占了90%。优化器采样时如果恰好抽到几个PENDING的行,会以为status的选择性很高,选了这个索引。实际执行时,90%的行都要回表,回表次数远超预期。

偏差二:回表代价被低估。

优化器估算回表代价时,用的是固定的成本常数。但真实的回表代价取决于数据在磁盘上的分布——如果主键是随机的(UUID),回表就是随机I/O;如果主键是自增的,回表可能是顺序I/O。优化器不区分这两种情况。

我那次加索引反而变慢,就是踩了第二个偏差。新加的索引选择性看起来不错,但回表的主键是随机的,每次回表都是随机I/O,几十万次随机I/O的开销,远超全表扫描的顺序读。

二、Index Merge:看起来聪明,实际很贵

Index Merge是另一个容易踩坑的场景。

当WHERE条件涉及多个索引列时,优化器可能选择Index Merge——同时扫描两个索引,取交集或并集。听起来很合理,但实际上合并操作本身的开销可能超过全表扫描。

SELECT * FROM orders

WHERE user_id = 12345 AND status = 'PAID';

如果user_id和status各有一个单列索引,优化器可能同时扫描两个索引,取交集。但如果user_id=12345返回10万行,status='PAID'返回50万行,交集运算要处理这两个结果集——合并、去重、排序,开销可能比直接全表扫描还大。

更危险的是Union类型的Index Merge(OR条件触发)。两个索引的结果集取并集,去重和排序的代价更高。

Index Merge的代价藏在三个地方:多次索引扫描、结果集在内存中的合并运算、以及多次回表。优化器在估算时,往往低估了合并运算和多次回表的开销。

三、怎么确认优化器选错了

方法一:EXPLAIN ANALYZE对比估算与实际

EXPLAIN ANALYZE

SELECT * FROM orders

WHERE user_id = 12345 AND status = 'PAID';

MySQL 8.0.18+的EXPLAIN ANALYZE会输出每个步骤的实际执行行数和时间。对比rows(估算)和actual rows(实际),如果差距很大(比如差10倍以上),说明基数估算有偏差。

方法二:看Extra列的Index Merge信号

EXPLAIN SELECT * FROM orders

WHERE user_id = 12345 OR status = 'PAID';

如果Extra列出现Using union(...)或Using intersect(...),说明触发了Index Merge。这时候要评估合并的代价是否真的比全表扫描低。

方法三:用FORCE INDEX验证

-- 强制走单个索引,对比响应时间

SELECT * FROM orders FORCE INDEX(idx_user_id)

WHERE user_id = 12345 AND status = 'PAID';

如果强制走单个索引比让优化器自由选择更快,说明优化器选错了。这是最直接的验证方法。

四、解决方案

方案一:更新统计信息。

ANALYZE TABLE orders;

80%的索引选错问题,更新统计信息就能解决。优化器拿到准确的基数估算后,会重新评估代价。

方案二:建更匹配查询模式的联合索引。

-- 替代两个单列索引 + Index Merge

CREATE INDEX idx_user_status ON orders(user_id, status);

联合索引一次扫描就能定位到目标行,不需要Index Merge的合并运算,也不需要多次回表。这是比Hint更根本的解法。

方案三:删掉低效索引。

如果某个索引建了之后,优化器频繁选错,而且实际执行效果很差,考虑删掉它。索引不是越多越好——每个索引都会影响优化器的选择空间。

方案四:用FORCE INDEX作为临时方案。

确认优化器选错后,用FORCE INDEX强制走正确的索引。但这是临时方案——数据分布变化后,Hint可能产生反效果。

五、小结

索引建了查询反而变慢,根因往往在优化器的选择:基数估算偏差导致它选了“看起来选择性高”但回表代价巨大的索引,或者触发了Index Merge,合并运算的开销超过全表扫描。排查的核心是用EXPLAIN ANALYZE对比估算与实际、看Extra列是否出现Index Merge、用FORCE INDEX验证。解决方案优先更新统计信息和建联合索引,FORCE INDEX只是临时手段。

那次加索引反而变慢之后,我改掉了“慢就加索引”的习惯。加索引之前先看执行计划,加完之后用EXPLAIN ANALYZE验证实际效果。索引是工具,不是信仰。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~

本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。

相关 AI 工具

AI Text Cleaner

AI Text Cleaner 可在浏览器中清除草稿中的隐藏 Unicode 字符、零宽空格和残留 Mark

Margo

Margo 是面向同人写作者和原创角色创作者的 AI 灵感写作工具,可围绕角色、关系和背景生成结构化人物设定

wan 3.5

wan 3.5 是浏览器端 AI 视频创作工具,支持从文字提示或参考图像生成视频,适合产品展示、角色场景、社

知乎直答

知乎推出的AI搜索引擎,直达问题答案

相关免费羊毛

DeepSeek

新用户免费试用额度

Kiro

AWS 智能体 IDE 免费档:每月 50 积分

NVIDIA Inception

NVIDIA Inception 初创 GPU/云折扣最高 $15k

NVIDIA NIM

免费托管 API 试用,支持自托管

同源资讯

Coding Agent 的知识也会漂移:用固定快照检查 Vercel Plugin 升级

掘金 · 10-23

云栖实录|Hologres 5.0全新发布:Agent Native 的全模态实时数仓

掘金 · 10-23

用 AI 做 PPT 被辞退,法院判公司赔:员工贴进对话框的数据,到底谁负责?

掘金 · 10-23

企业知识库 RAG 实战:混合检索、权限过滤与答案溯源

掘金 · 10-23

以实时驱动智能:Apache Doris Summit 2026 盛大落幕,开启 Agentic AI 时代数据

掘金 · 10-23

花椒 AI 应用工程化实践:灵泡泡的工作流、版本管理与任务恢复

掘金 · 10-23

阅读原文(juejin.cn)

标题、摘要与正文均来自该信源的公开页面,版权归原作者所有;本站自动聚合与翻译,仅作出处跳转。

← 返回 AI 资讯列表AI工具图鉴首页

返回 AI图鉴首页 · 提交工具 · 商务合作 · 关于本站 · 588 款 AI 工具分类导航 · 每日 AI 热门项目与动态