当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL 索引合并为什么不如联合索引:OR 条件的执行计划与改写边界

MySQL 索引合并为什么不如联合索引:OR 条件的执行计划与改写边界

来源:17golang原创 2026-08-25 09:01:41 0浏览 收藏

订单筛选接口突然变慢时,SQL 里一个看似方便的 OR 往往比单列索引少走了一大段路。MySQL 可能选择 Index Merge,把两个索引扫描结果合并后再回表;如果回表行很多、还要排序,这条路径就可能不如一个贴合查询条件的联合索引。判断依据不是看到 “Using union” 就下结论,而是对比估算行数、实际扫描量和结果是否需要额外排序。

要点速览
  • Index Merge 是一种可用的访问策略,不是联合索引的通用替代品。
  • 两个单列索引分别找出大量候选行后,合并、去重和回表都要付成本。
  • 先用 EXPLAIN 看计划,再用 EXPLAIN ANALYZE 核对实际行数和耗时。
  • 改成联合索引或 UNION ALL 前,必须确认条件选择性、结果重复和排序语义。

日常写带OR条件的查询时,不少人会碰到MySQL选了索引合并策略,实际运行耗时却远超出预期,大部分场景下这种方案的综合开销比合理设计的联合索引高不少,我们结合实际执行流程把这类查询的判定边界讲清楚。MySQL OR 条件触发两个单列索引扫描并产生合并回表成本的订单查询示意图

先看清 Index Merge 到底合并了什么

假设订单表有客户、状态和创建时间三个常用过滤字段:

CREATE TABLE orders (
  id BIGINT PRIMARY KEY,
  customer_id BIGINT NOT NULL,
  status VARCHAR(16) NOT NULL,
  created_at DATETIME NOT NULL,
  KEY idx_customer (customer_id),
  KEY idx_status (status)
);

查询想找出某个客户的订单,或者最近处于待支付状态的订单:

SELECT id, customer_id, status, created_at
FROM orders
WHERE customer_id = 128
   OR status = 'pending'
ORDER BY created_at DESC
LIMIT 50;

优化器可能分别扫描 idx_customeridx_status,把两边得到的行位置合并,再回到聚簇索引取完整记录。这个策略在两个条件都很有选择性时可能合适;一旦某个状态占了大半张表,第二条索引路径就会带来大量候选行。

为什么两个单列索引会输给一个联合索引

合并之前已经产生了两批候选行

Index Merge 的成本不只是一条额外的合并操作。每个索引都要先找到候选记录,之后还可能进行排序、去重和回表。查询只返回 50 行,并不代表数据库只读了 50 行;LIMIT 通常要等候选结果完成排序或筛选后才能生效。

MySQL Index Merge 与联合索引改写后的 EXPLAIN 和 EXPLAIN ANALYZE 对比路径

联合索引也不是把所有字段都堆进去

如果真实查询是“客户 + 时间范围”,更直接的索引通常是 (customer_id, created_at),而不是为了覆盖所有可能的 OR 条件盲目添加大索引:

ALTER TABLE orders
  ADD KEY idx_customer_time (customer_id, created_at);

联合索引遵循最左前缀规则。它能帮助客户条件先缩小范围,再按创建时间读取;但它并不会自动让 customer_id = 128 OR status = 'pending' 变成一条连续的索引范围。索引设计必须跟真实查询形状一起看。

用 EXPLAIN 找到真正的成本信号

先固定同一组参数,分别观察访问类型、使用的键和估算行数:

EXPLAIN FORMAT=TREE
SELECT id, customer_id, status, created_at
FROM orders
WHERE customer_id = 128 OR status = 'pending'
ORDER BY created_at DESC
LIMIT 50;

重点看 possible_keyskeyrowsExtra。看到 Index Merge 只是事实描述,不等于性能结论;还要确认合并后的候选量、是否出现额外排序,以及返回列是否需要频繁回表。

信号通常说明下一步
rows 很大至少一个 OR 分支选择性差检查值分布与条件是否可拆
Using filesort结果顺序不能直接由访问路径提供评估排序字段和索引顺序
Index Merge + 大量回表两条索引只筛出行位置检查是否适合覆盖或联合索引

然后在低风险环境执行:

EXPLAIN ANALYZE
SELECT id, customer_id, status, created_at
FROM orders
WHERE customer_id = 128 OR status = 'pending'
ORDER BY created_at DESC
LIMIT 50;

把实际读取行数和耗时记下来,再跟 EXPLAIN 的估算值对照。估算偏差很大时,先检查统计信息和数据分布,别急着把问题归结为索引类型。

三种改写方式,各自有边界

把同一业务条件改成联合索引

当实际需求是客户、时间和状态的固定组合时,优先把最常用的等值条件放在前面:

ALTER TABLE orders
  ADD KEY idx_customer_status_time (customer_id, status, created_at);

先用一组真实查询验证,不要只凭字段数量猜索引效果。联合索引会增加写入和存储成本,状态值高度重复时也不一定值得单独放在最前面。

用 UNION ALL 拆开两个互斥分支

当两个分支可以分别利用索引,并且业务能接受合并结果后再排序,可以考虑拆查询。若两边可能命中同一订单,不能直接把 OR 替换为 UNION ALL,否则会重复返回;改用 UNION 或增加互斥条件又会引入去重成本。

SELECT id, customer_id, status, created_at
FROM orders WHERE customer_id = 128
UNION ALL
SELECT id, customer_id, status, created_at
FROM orders WHERE status = 'pending' AND customer_id  128;

只在证据充分时调整优化器开关

为了验证假设,可以在测试环境临时比较关闭 Index Merge 前后的计划;但这不是生产修复。数据量和分布变化后,今天更快的访问策略可能变成明天的负担,最终还是应该回到查询形状、索引和统计信息上。

线上排查可以按这张清单走

  1. 保存原始 SQL、参数值范围和返回列,确保对比实验只改一个变量。
  2. 检查 EXPLAIN 中的 key、rows、Extra,确认是否为 Index Merge、是否排序和回表。
  3. 用两组选择性不同的数据执行 EXPLAIN ANALYZE,记录实际行数和耗时。
  4. 根据真实查询形状试验联合索引或互斥的 UNION ALL,验证结果不重复。
  5. 灰度观察慢查询和写入开销,确认索引收益没有转化成更新成本。

常见问题与回归边界

看到 Index Merge 就应该关闭它吗?

不应该。它在选择性好的条件下可能是合理方案,是否需要改写要看实际扫描行数、回表量和排序耗时。

联合索引一定比两个单列索引快吗?

不一定。联合索引只对匹配的查询形状有效,还会增加写入和存储成本。应使用代表性数据和 EXPLAIN ANALYZE 验证。

OR 改成 UNION ALL 会不会重复数据?

会有这个风险。两个分支命中同一行时必须增加互斥条件,或选择能去重的写法,并重新核对排序和分页语义。

rows 很大是不是一定说明索引失效?

不是。rows 是优化器估算的候选量,可能受统计信息和数据分布影响;实际情况要结合 EXPLAIN ANALYZE 判断。

把一次慢查询变成可回归的检查

索引合并与联合索引不是简单的二选一。先确认 OR 两侧各自的选择性,再用执行计划解释“读了什么”,最后用实际运行结果确认“真的花了多少”。只有查询结果、排序、重复数据和写入成本都通过回归,改写才算完成。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
MySQL 预编译参数为什么会换查询计划:prepared statement 与类型转换边界MySQL 预编译参数为什么会换查询计划:prepared statement 与类型转换边界
上一篇
MySQL 预编译参数为什么会换查询计划:prepared statement 与类型转换边界
CSS @starting-style 首次显示动画怎么做:popover 与 dialog 的初始状态和降级边界
下一篇
CSS @starting-style 首次显示动画怎么做:popover 与 dialog 的初始状态和降级边界
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ljg-skills -
    ljg-skills
    ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
    5243次使用
  • MELO音乐 - AI 音乐生成平台,支持多模态创作能力
    MELO音乐
    MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
    4756次使用
  • UniScribe - AI 免费在线音视频转文字平台
    UniScribe
    UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
    4703次使用
  • 剧云 - 免费 AI 智能中文剧本创作平台
    剧云
    剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
    4958次使用
  • 万象有声 - AI 一站式有声内容创作平台
    万象有声
    万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
    4915次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码