为什么MySQL子查询中的OR会大幅降低查询性能?附优化案例解析
性能差异核心原因
你遇到的性能差异本质是JOIN条件中的OR语句触发了MySQL的全表扫描回退机制,拆分两个LEFT JOIN加COALESCE的写法可以完全命中联合索引,避免了千万级数据的全表遍历。
执行计划差异解读
原始慢查询执行计划问题点
看financial_transactions(ft)表对应的执行计划行:
type字段值为ALL:代表对千万级(12587878行)的ft表做全表扫描,这是耗时高的核心来源Extra字段值为Range checked for each record (index map: 0x2):意味着MySQL无法直接使用你建的transactions_unique_by_commercial_doc联合索引做JOIN匹配,只能为前面关联得到的每一行结果,单独到ft表做一次范围检查,相当于关联N行就要在千万级表上扫N次,效率极低
你单独执行子查询速度快,是因为子查询单独执行时优化器会优先过滤ft表的数据再做关联,但子查询和外层表关联时,优化器选择了嵌套循环关联顺序,导致ft表需要逐行匹配外层数据,性能就会指数级下降。
优化后查询执行计划优势
看ft_one、ft_two对应的执行计划行:
- 两个表的
type字段值都是ref:代表两次关联都命中了transactions_unique_by_commercial_doc联合索引,每次关联仅需查询1行数据 - 无全表扫描操作,所有关联步骤都走索引匹配,千万级ft表不需要做全量遍历,耗时自然大幅下降
优化逻辑原理
你使用的拆分方案是处理JOIN条件OR性能问题的标准方案:
- 把
JOIN ft ON (条件A) OR (条件B)拆为两次独立的LEFT JOIN:LEFT JOIN ft_one ON 条件A、LEFT JOIN ft_two ON 条件B,两次关联的条件都可以独立匹配联合索引,不会出现索引失效问题 - 用
COALESCE(ft_one.conversion_rate, ft_two.conversion_rate)取非空的转换率,和原始OR逻辑完全等价:只要任意一个条件匹配就取对应值,两个都未匹配则返回NULL,完全符合原有业务逻辑
另外原始查询存在一个隐形逻辑问题:你对ft表声明了LEFT JOIN,但WHERE条件中加了ft.company_id = 14792,相当于把LEFT JOIN转为了INNER JOIN,无匹配的行都会被过滤。优化后你把company_id条件移到了LEFT JOIN的关联条件中,更符合LEFT JOIN语义,如果业务需要过滤掉无转换率的行,只需要在子查询WHERE中加coalesce(ft_one.conversion_rate, ft_two.conversion_rate) IS NOT NULL即可,不会影响性能。
内容的提问来源于stack exchange,提问作者user2320239
相关产品推荐
相关产品推荐

