优化 OR 条件过多导致的查询超时

news2024/9/21 20:41:30

优化 OR 条件过多导致的查询超时

文章目录

    • 优化 OR 条件过多导致的查询超时
        • 背景
        • 问题分析
        • 方案分析
          • 方案一:入参去重
          • 方案二:分页或者分批查询
          • 方案三:UNION 代替 OR
          • 方案四:IN 代替 OR
            • 1. 分别对列进行 `IN` 查询,在代码中进行数据筛选。
            • 2. 对多列进行 `IN` 查询。
            • 小结
          • 方案五:JOIN 查询(采用)
        • 采用方案要点分析
          • 联合索引的顺序
          • 条件查询的顺序
          • 精简查询字段
          • LEFT JOIN 使用中间表
        • 总结

背景

在生成年度累积报表时,需要根据排产订单中的【工厂、物料、BOM版本】进行分组查询。尤其是在数据量较大的情况下(例如 2024 年 1-7 月的排产数据),生成报表时遇到了查询超时问题。这主要是由于分组查询后的数据量较大,且查询中使用了大量的 OR 条件。

本文将探讨针对这种场景的优化策略,并提供不同的解决方案以供参考。具体使用哪种方案,应根据实际业务需求及数据量大小来选择。


问题分析

以下代码片段展示了问题的根源。在处理过程中,代码基于输入参数列表循环拼接 OR 条件。由于输入的列表包含了数千条记录,这种大量的 OR 条件在数据库中执行时导致了性能问题,最终导致查询超时。

// 自制UPH单价
List<PriceBillZzUphInfo> priceBillZzUphInfoParamList = orwAnnualProductionOrderList.stream()
        .map(item -> new PriceBillZzUphInfo()
                .setFactoryCode(item.getProductFactoryCode())
                .setMaterialParentCode(item.getProductMaterialCode())
                .setBomVersion(item.getBomVersion())
        )
        .collect(Collectors.toList());
List<PriceBillZzUphInfo> priceBillZzUphInfoResultList = this.priceBillZzUphInfoProviderService.getByMatAndFacAndBom(priceBillZzUphInfoParamList);


// getByMatAndFacAndBom
public List<PriceBillZzUphInfo> getByMatAndFacAndBom(List<PriceBillZzUphInfo> list) {
    if (CollUtil.isEmpty(list)) {
        return Collections.emptyList();
    }

    LambdaQueryWrapper<PriceBillZzUphInfo> lambdaQueryWrapper = Wrappers.lambdaQuery();
    lambdaQueryWrapper
            .and(queryWrapper -> {
                queryWrapper.eq(PriceBillZzUphInfo::getStatus, 1);
            })
            .and(queryWrapper -> {
                list.forEach(item -> {
                    queryWrapper.or(query -> {
                        query.eq(PriceBillZzUphInfo::getMaterialParentCode, item.getMaterialParentCode());
                        query.eq(PriceBillZzUphInfo::getFactoryCode, item.getFactoryCode());
                        query.eq(PriceBillZzUphInfo::getBomVersion, item.getBomVersion());
                    });
                });
            });

    return list(lambdaQueryWrapper);
}

在该场景中,orwAnnualProductionOrderList 涉及的数据时间范围是[上个月所在年的第一天, 上个月的最后一天],该时间范围内的数据需要根据【工厂代码】、【物料代码】和【BOM版本】进行多条件查询。

生成的 SQL 如下:

SELECT *
FROM price_bill_zz_uph_info
WHERE deleted = 1
  AND ((status = 1) AND ((material_parent_code = '0031800390C' AND factory_code = '80G0' AND bom_version = '02') OR
                         (material_parent_code = '0021800126A' AND factory_code = '80K0' AND bom_version = '10') OR
                         .........;

方案分析

该问题的最终定位便是 SQL 的查询超时,所以解决方案也是围绕解决该 SQL 的查询效率问题,可以从 SQL 和业务等方面进行优化。

方案一:入参去重

直接原因是入参过多,导致了 SQL 拼接条件过多。所以先对入参进行去重,但是去重后的入参仍然有七千多条,仍旧超时。所以该方案不可行。

方案二:分页或者分批查询

使用分页查询,因为不确定具体的符合条件的数据量,所以无法简便且准确的进行分页查询。

分批查询,将入参进行分批,比如每一千条或一百条查询一次,该方案可以控制在有效时间内返回数据并进行处理。但是该方案会造成多次查询数据库,且 OR 条件过多会导致索引失效,在数据量较大时,查询效率仍旧会低。

所以,分页或者分批查询在本次业务场景下可行性低。

方案三:UNION 代替 OR

使用 UNION 来代替 OR 条件查询,这是在进行 SQL 优化时的一种方案。但是在本次业务背景下,OR 条件查询参数过多,会造成很多 union 拼接,并不合理。

方案四:IN 代替 OR

使用 IN 条件语句代替 OR 条件查询有两种形式:

1. 分别对列进行 IN 查询,在代码中进行数据筛选。
queryWrapper.in(PriceBillZzUphInfo::getMaterialParentCode, materialCodeList)
        .in(PriceBillZzUphInfo::getFactoryCode, factoryCodeList)
        .in(PriceBillZzUphInfo::getBomVersion, bomVersionList);

在 mybatis-plus 中使用如上的 Wrapper 条件,查询出所有数据后,再根据入参进行有效数据的获取。该方案可以正常获取数据并进行处理,但是 IN 条件查询参数过多时会造成索引失效,导致效率较慢。

2. 对多列进行 IN 查询。

在 SQL 层面使用多条件 IN 查询。XML 文件内容如下:

SELECT material_parent_code, factory_code, bom_version, qt_attr2_price
FROM price_bill_zz_uph_info
WHERE status = 1 AND deleted = 1 AND (material_parent_code, factory_code, bom_version) IN
<foreach collection="list" item="item" open="(" separator="," close=")">
    (#{item.materialParentCode},#{item.factoryCode},#{item.bomVersion})
</foreach>

对应的 SQL语句如下:

SELECT material_parent_code, factory_code, bom_version, qt_attr2_price
FROM price_bill_zz_uph_info
WHERE status = 1 AND deleted = 1 AND (material_parent_code, factory_code, bom_version) IN
      (('0041800262KA', '8783', '03'), ('0041800808', '8710', '03'), ('0041800808', '8710', '03')......);

查看执行计划:

在这里插入图片描述

查看执行计划发现可以使用索引,而且执行时间要比原 SQL 短,但是依旧要几秒甚至十几秒。

小结

使用 IN 条件查询来代替 OR 可以在一定程度上缓解查询慢的问题,但是在该业务场景下,更多的原因是因为入参的数据量比较大,所以 I/O 耗时严重,而且 MySQL 的服务器层在解析优化该 SQL 时耗时也会多一些,各方面原因导致了整体耗时仍旧较高。

方案五:JOIN 查询(采用)

使用 JOIN 查询,直接在 SQL 中进行时间范围的控制,在 ON 条件中进行参数控制,并添加如下索引:

CREATE INDEX index_mat_factory_bom_attr2
    ON price_bill_zz_uph_info (material_parent_code, factory_code, bom_version, qt_attr2_price);
    
CREATE INDEX idx_mat_factory_bom
    ON inf_orw_annual_production_order (product_material_code, product_factory_code, bom_version);

SQL 语句:

SELECT t.material_parent_code, t.factory_code, t.bom_version, t.qt_attr2_price
FROM price_bill_zz_uph_info t
         LEFT JOIN (SELECT product_factory_code, product_material_code, bom_version
                    FROM inf_orw_annual_production_order
                    WHERE orw_actual_end_time BETWEEN #{beginTime} AND #{endTime}) o
                   ON t.material_parent_code = o.product_material_code AND
                      t.factory_code = o.product_factory_code AND t.bom_version = o.bom_version
GROUP BY t.material_parent_code, t.factory_code, t.bom_version;

查看执行计划,发现均可以使用索引:

)


采用方案要点分析

上述方案五中,要注意以下几点:

联合索引的顺序

联合索引的列顺序是物料、工厂、BOM 版本,因为数据的区分度是物料>工厂>BOM 版本,以该方式建立索引,可以提高索引的有效利用率。
在这里插入图片描述

条件查询的顺序

条件查询列的顺序要跟联合索引的列顺序对应,防止索引失效,要遵循最左匹配原则。

精简查询字段

在该需求中,因为只需要 t.material_parent_code, t.factory_code, t.bom_version, t.qt_attr2_price 这四列的值即可,所以对于 price_bill_zz_uph_info 表建立的联合索引也多加了一列 qt_attr2_price,这样做的目的是为了减少 MySQL 的回表。

要根据具体的需求来确定索引,如果查询条件列过多,并不适合将所有的列全部放到索引中,因为维护索引也有资源和性能的损耗。

LEFT JOIN 使用中间表

不是直接 JOIN inf_orw_annual_production_order 表,像如下 SQL 会造成索引失效。

SELECT t.*
FROM price_bill_zz_uph_info t
         LEFT JOIN inf_orw_annual_production_order o
                   ON  t.material_parent_code = o.product_material_code AND
                       t.factory_code = o.product_factory_code AND t.bom_version = o.bom_version
WHERE o.orw_actual_end_time BETWEEN '2024-01-01 00:00:00' AND '2024-07-31 23:59:59'
GROUP BY t.material_parent_code, t.factory_code, t.bom_version;

查看执行计划:

在这里插入图片描述


总结

在大数据量、高复杂度的查询场景下,简单的 OR 条件可能导致严重的性能问题。通过合理的查询优化策略,如批量处理、索引优化、使用临时表、以及查询重构,可以显著改善查询性能,避免超时问题。

这些优化方法应结合业务需求和实际数据量灵活应用,以确保在维持系统性能的同时满足业务需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2142704.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

同一Python脚本中训练多个模型时的 wandb 配置错误解决方案

文章目录 摘要背景介绍报错信息wandb 模型训练名 摘要 在机器学习项目中&#xff0c;使用Python脚本训练多个模型时&#xff0c;可能会遇到WandB&#xff08;Weights and Biases&#xff09;配置错误&#xff0c;尤其是在训练多个模型参数大小不一致的情况下。 本文将介绍如何…

Vue学习记录之三(ref全家桶)

ref、reactive是在 setup() 声明组件内部状态用的&#xff0c; 这些变量通常都要 return 出去&#xff0c;除了供 < template > 或渲染函数渲染视图&#xff0c;也可以作为 props 或 emit 参数 在组件间传递。它们的值变更可触发页面渲染。 ref &#xff1a;是一个函数&…

Get包中的根组件

文章目录 1. 知识回顾2. 使用方法2.1 源码分析2.2 常用属性 3. 示例代码4. 内容总结 我们在上一章回中介绍了"Get包简介"相关的内容&#xff0c;本章回中将介绍GetMaterialApp组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 知识回顾 我们在上一章回中已经…

Unity webgl跨域问题 unity使用nginx设置跨域 ,修改请求头

跨域 什么是跨域 跨域是指浏览器因安全策略限制&#xff0c;阻止一个域下的网页访问另一个域下的资源。 一些常见的跨域情况&#xff1a; 协议不同 从 http://example.com 请求 https://example.com。域名不同 从 http://example.com 请求 http://anotherdomain.com。端口不…

Village Exteriors Kit 中世纪乡村房屋场景模型

此模块化工具包就是你一直在寻找的适合建造所有中世纪幻想村庄和城市建筑所需要的工具包。 皇家园区 - 村庄外饰套件的模型和纹理插件资源包 酒馆和客栈、魔法商店、市政大厅、公会大厅、布莱克史密斯锻造厂、百货商店、珠宝商店、药店、草药师、银行、铠甲、弗莱切、马厩、桌…

list从0到1的突破

目录 前言 1.list的介绍 2.list的常见接口 2.1 构造函数&#xff08; (constructor)&#xff09; 接口说明 2.2 list iterator 的使用 2.3 list capacity 2.4 list element access 2.5 list modifiers 3.list的迭代器失效 附整套练习源码 结束语 前言 前面我们学习…

Defining Constraints with ObjectProperties

步骤4&#xff1a;使用对象定义约束 物业 您可以创建时间和放置约束&#xff0c;如本教程所示。你也可以 更改单元格的属性以控制Vivado实现如何处理它们。许多 物理约束被定义为单元对象的属性。 例如&#xff0c;如果您在设计中发现RAM存在时序问题&#xff0c;为了避免重新合…

C语言代码练习(第二十六天)

今日练习&#xff1a; 数据的交换输出输入 n 个数&#xff0c;找出其中最小的数&#xff0c;将它与最前面的数交换后输出这些数 输入一个英文句子&#xff0c;将每个单词的第一个字母改成大写字母 输入一个十进制数 N &#xff0c;将它转换成 R 进制数输出 数据的交换输出输入 …

阿里OSS对象存储服务,实现图片上传回显

阿里OSS对象存储服务 OSS服务1. 创建buckte2. 获取accesskey3. 参照官方SDK编写程序安装SDK 4. 程序编写5. 封装6. 在spring中调用 OSS服务 阿里云对象存储 OSS&#xff08;Object Storage Service&#xff09;是一款海量、安全、低成本、高可靠的云存储服务&#xff0c;提供最…

利用JS数组根据数据生成柱形图

要求 <html> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document…

精准识别,高效管理:工服识别AI检测算法在多场景中的应用优势

随着人工智能技术的快速发展&#xff0c;其在各个行业的应用也日益广泛。特别是在工业生产和安全监管领域&#xff0c;工服识别AI检测算法凭借其高效、精准的特点&#xff0c;成为提升生产效率、保障工作人员安全的重要手段。本文将详细介绍TSINGSEE青犀AI智能分析网关V4工服识…

Hibernate基础

Hibernate基础总结 有利的条件和主动的恢复产生于再坚持一下的努力之中&#xff01; 好久没更新了&#xff0c;今天入门了Hibernate&#xff0c;由于之前学习了MyBatis&#xff0c;初步感觉二者的底层实现思想有很多相似之处&#xff0c;下面让我们以一个入门Demo的形式感受一…

3.Java高级编程实用类介绍(一)

三、Java高级编程实用类介绍(一) 文章目录 三、Java高级编程实用类介绍(一)一、枚举类型二、包装类三、Math 一、枚举类型 使用enum进行定义 public enum 枚举名字{值1,值2.... }二、包装类 每个基本类型在java.lang包中都有一个相应的包装类 /** new包装类&#xff08;字符…

【C++笔记】类和对象的深入理解(三)

【C笔记】类和对象的深入理解(三) &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】类和对象的深入理解(三)前言一.日期类的实现1.1声明和定义分离1.2日期类整数1.3日期类整数1.4日期类-整数1.5日期类-日期1.6复用对…

并发安全与锁

总述 这篇文章&#xff0c;我想谈一谈自己对于并发变成的理解与学习。主要涉及以下三个部分&#xff1a;goroutine&#xff0c;channel以及lock 临界区 首先&#xff0c;要明确下面两组概念 并发和并行 并行&#xff1a;指几个程序每时每刻都同时进行 并发&#xff1a;指…

lnmp - 登录技术方案设计与实现

概述 登录功能是对于每个动态系统来说都是非常基础的功能&#xff0c;用以区别用户身份、和对应的权限和信息&#xff0c;设计出一套安全的登录方案尤为重要&#xff0c;接下来我介绍一下常见的认证机制的登录设计方案。 方案设计 HTTP 是一种无状态的协议&#xff0c;客户端…

iOS - TestFlight使用

做的项目需要给外部人员演示&#xff0c;但是不方便获取对方设备的UDID&#xff0c;于是采用TestFlight 的方式邀请外部测试人员的方式给对方安装测试App&#xff0c;如果方便获取对方设备的UDID&#xff0c;可以使用蒲公英 1.在Xcode中Archive完成后上传App Store Connect之前…

浙大上交联合阿里腾讯,共同构建医学AI领域的顶尖科研+商业团队|个人观点·24-09-17

小罗碎碎念 昨晚锻炼时&#xff0c;我想着是时候对推文的内容做一些改进了——既能通过写推文来锻炼自己写paper的能力&#xff0c;也希望凭借自己一点微弱的影响力&#xff0c;去带动更多的人加入医学AI的队伍中。 这一期推文系统且深度的分析一下&#xff0c;国内哪些学者在医…

Linux基础开发环境(git的使用)

1.账号注册 git 只是一个工具&#xff0c;要想实现便捷的代码管理&#xff0c;就需要借助第三方平台进行操作&#xff0c;当然第三平台也是基于git 开发的 github 与 gitee 代码托管平台有很多&#xff0c;这里我们首选 Github &#xff0c;理由很简单&#xff0c;全球开发者…

算法题之回文子串

回文子串 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 示例 1&#xff1a; 输入&#xff1a;s "abc" 输出&#xff1a;3 解释…