当前位置: 首页 > news >正文

两个字段都建了单列索引,为什么加了 OR,执行计划还是全表扫描?

写 SQL 使用OR条件是非常常见的场景,为了优化这类查询,会特意为phoneemail两个字段分别创建单列索引。

SELECT * FROM users WHERE phone = '13800000000' OR email = 'xiaofu@qq.com';

但我们使用EXPLAIN查看该语句的执行计划,往往会发现type字段显示为ALL,说明 MySQL 最终还是走了全表扫描,没有使用我们创建的索引。

奇怪吧,明明两个字段都有索引,为什么用OR之后索引却失效了?

MySQL 优化器的成本计算

理解为什么索引失效前,先理解 MySQL 查询优化器的工作原理。MySQL 是基于成本的优化器,它在生成执行计划时,会估算各种执行路径的成本值,最终选择成本最低的路径。

传统的关系型数据库,MySQL 默认的读取路径一次查询只能使用一个索引。假设我们将查询限制在phone索引上,那么优化器会通过phone二级索引树快速定位到主键值,再通过主键值回表获取完整的行记录。

但在OR条件下,查询的语义是并集,也就是满足phone = '13800000000'或者email = 'xiaofu@qq.com'的数据都需要被找出来。

如果优化器只选择走phone索引,确实能快速找到phone匹配的行;但由于满足email条件的行可能散落在表的其他位置,为了不漏掉数据,MySQL 在通过phone索引查出部分数据后,仍然不得不对整张表进行一次全表扫描,找出满足email = 'xiaofu@qq.com'且不与phone重合的数据。

这时候优化器会对比两种执行路径的成本:

  • 路径 A(单索引 + 回表 + 全表扫描):扫描phone索引 + 回表获取记录 + 全表扫描查找email的记录。

  • 路径 B(直接全表扫描):从头到尾扫描一次全表,边扫描边过滤满足phoneemail条件的记录。

回表属于随机 I/O,而全表扫描属于顺序 I/O。在 MySQL 的成本计算中,一次随机 I/O 的权重默认是顺序 I/O 的几倍。如果回表的数据行数稍微多一点,路径 A 的估算成本就会远超路径 B。所以优化器会果断放弃索引,直接走全表扫描。

为什么索引合并没生效?

有同学会问:MySQL 不是有索引合并(Index Merge)机制,它能同时用两个索引,最后在内存里把结果合并吗?

OR条件下,MySQL 确实有个index_merge_union索引并集算法,它的处理流程:

  1. 二级索引扫描:通过 phone 索引树扫描出满足条件的主键 ID 集合 S1。由于二级索引的叶子节点本身是按照二级索引键值排序的,相同的二级索引键值,其叶子节点存储的主键 ID 默认是递增有序的。

  2. 二级索引扫描:通过 email 索引树扫描出满足条件的主键 ID 集合 S2,同样的其内部主键 ID 也是有序的。

  3. 并集去重:内存中将 S1 和 S2 进行去重合并,得到最终的主键集合。因为 S1 和 S2 天生有序,MySQL 可以使用高效的双指针归并算法以 O(N) 的时间复杂度快速完成合并。

  4. 有序回表:利用合并后的主键集合进行回表查询。这时主键 ID 已经是去重且有序的,回表操作可以从随机 I/O优化为顺序 I/O,极大地提高了读取效率。

都有这个机制,为什么实际开发很难看到它生效?这主要受限于几个硬性约束和成本考量:

算法对主键有序性的要求

index_merge_union算法之所以高效,核心在于并集去重操作能在 O(N) 时间复杂度内完成。这要求S1 和 S2 两个集合在扫描出来时必须是天生有序的

只有在等值查询,如phone = '138...'取出的主键 ID 才是按照主键大小递增排序的。

一旦出现范围查询,如phone LIKE '138%'phone > '138',由于二级索引键值不同,即使索引字段有序,但对应的主键 ID 在索引页中也是无序交错的。

这样 MySQL 无法直接利用双指针进行并集,必须引入Sort-Union算法,即先在内存中对主键 ID 进行排序,然后再做并集。但在内存排序对 CPU 和内存开销很大,优化器在估算成本后,通常会放弃索引合并,直接选择全表扫描。

优化器成本估算的临界值

即便OR两边都是等值查询,字段都有索引,优化器依然会很细致。

回表比例达到一定阈值(一般取决于表的数据量、页大小及系统负载),回表的随机 I/O 成本会呈指数级上升。优化器计算出索引合并的成本比一次性的全表扫描还要高,必然会选择全表扫描。

硬性失效

OR的底层逻辑是必须获取满足任意一方的所有数据:

隐式类型转换:如果 phone 在表中是VARCHAR类型,但在 SQL 中写成了数字WHERE phone = 13800000000,MySQL 会隐式地将字段值转换为浮点数再做比较,导致 phone 索引失效。

既然 phone 无法走索引,MySQL 就必须通过全表扫描来找出满足 phone 条件的行,整个查询因此退化为全表扫描。

包含未建索引的列:如果 SQL 包含没有索引的字段WHERE phone = '138...' OR age = 18,因为age没有索引,数据库无论如何都要进行全表扫描以过滤age = 18的数据,所以phone索引同样会被放弃。

大厂的 SQL 优化方案

为了规避OR的索引失效和优化器成本估算不准,实际开发可以用以下两种更稳妥的优化方案。

UNIONUNION ALL代替OR(首选)

这是我最推荐、执行计划最稳定的改写方式,我们可以将OR查询拆分为两个独立的子查询,然后使用UNION进行连接:

SELECT * FROM users WHERE phone = '13800000000' UNION ALL SELECT * FROM users WHERE email = 'xiaofu@qq.com';

为什么这种方案更优?

  1. 没有单索引限制:拆分后两个子查询是完全独立的,第一个子查询可以稳定、高效地使用 phone 索引,第二个子查询可以稳定使用 email 索引。

  2. 避免优化器估算失准:单索引查询的成本估算非常精准,MySQL 不用去纠结复杂的Index Merge成本。

  3. 优先使用UNION ALL提升性能:UNION会在内存中创建一个临时表,对结果集进行去重排序,带来额外的 CPU 和内存开销。如果业务上可以容忍重复数据,或者逻辑上两个条件的结果集本身就是互斥的,例如 phone 和 email 匹配到的不可能是同一行,强烈建议使用 UNION ALLUNION ALL只做结果集拼接,不进行任何去重和排序性能高。

覆盖索引

如果你的业务场景不需要SELECT *,只需要获取索引列本身

SELECT id, phone, email FROM users WHERE phone = '13800000000' OR email = 'xiaofu@qq.com';

由于查询的字段(idphoneemail)已经全部包含在二级索引中,MySQL扫描索引后不需要进行回表。 没有了随机 I/O 成本,索引扫描和内存合并的开销就变得极廉价,MySQL 优化器 100% 会触发index_merge_union避免全表扫描。

说在最后

SQL 优化的核心,就是让 SQL 的执行路径更简单,增加 SQL 查询的确定性。包含OR条件的组合查询,经常因为回表成本的权衡,导致优化器选择保守的全表扫描。

多索引 OR 查询建议使用UNION ALL进行改写,将复杂的、充满不确定性的多条件 OR 查询,拆解为确定性更高、执行路径更清晰的单索引查询,可以保证 SQL 查询的稳定性。

http://www.cnnetsun.cn/news/4278533.html

相关文章:

  • Agent Skills 入门到实战:从 Prompt 到可复用技能封装
  • 毕业论文降 AI 什么时候该花钱?快降重 VS 笔灵 AI,教育学硕士知网 AIGC 实测避坑
  • AI时代开发者进阶指南:从Prompt到大模型工程实践
  • GraphRAG实战:基于代码知识图谱的代码库问答实现
  • 硬盘健康监控与故障预警:用Hard Disk Sentinel看懂SMART数据
  • AI应用盈利难?从算力成本到工程优化的实战指南
  • 基于Mahout协同过滤的电影推荐系统:Java工程实践与毕业设计指南
  • WordPress浏览量计数器插件:精准统计、缓存兼容与性能优化全攻略
  • Python学习路线全解析:爬虫、数据分析、AI与自动化办公实战指南
  • 英伟达70%营收预期下,AI算力规划与GPU部署实战指南
  • 2026年Java零基础暑期学习路线:从JDK安装到项目实战全攻略
  • 层次分析法实战指南:从多准则决策到结构化选择
  • 【已解决】docker desktop安装求助!!
  • 元初混沌体系 第三卷 卫星互联网全域周天拓扑体系:第五十四篇 中轨周天骨干层全场景拓扑闭环总结
  • 可解释AI与局部蒸馏:用随机森林与线性回归实战详解
  • MySQL 表的操作实战指南:创建、修改与删除
  • 单片机毕业设计-基于 STM32 单片机的车载温碳监测与智能通风控制系统设计 基于 STM32 的车内人员检测与环境智能调控装置设计(013605)
  • 基于LLM的双维度题目附带内容相似度分析框架解析
  • 拼多多 OCPX 稳定成本推广:一阶段、二阶段深度解析
  • 海鲜池开缸、巡检、换水与应急处理:一套可量化的日常操作规程
  • ASP.NET WebForms三层架构实战:从虚拟主机销售系统源码看经典B/S应用开发
  • 网易有道2018校招算法工程师笔试复盘:考点、编程题与备考策略
  • Kafka架构原理与面试实战:从高性能到可靠性全解析
  • Linux进程管理全面解析
  • Windows系统清理与提速:从底层原理到命令行实战指南
  • 单片机毕设项目:基于 STM32 单片机的户外多险情实时监测报警平台设计 基于 STM32 的危险等级可视化户外安全防护设备开发(013505)
  • 【原创开源】 多级串联滚轴递进式逐层剥离石墨烯连续量产装置及方法|民间独立工程推演
  • DeepTutor:基于RAG的智能教育辅导与知识库问答部署指南
  • 智能体能自动干活吗?任务、工具、记忆和人工确认一次讲清
  • 小鹏机器人估值430亿背后:具身智能赛道的价值锚点与评估逻辑