首页 > 其他分享 >优化 OR 条件过多导致的查询超时

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

时间:2024-09-17 20:21:48浏览次数:3  
标签:code 查询 material factory 过多 version bom 超时

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

文章目录

背景

在生成年度累积报表时,需要根据排产订单中的【工厂、物料、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 条件可能导致严重的性能问题。通过合理的查询优化策略,如批量处理、索引优化、使用临时表、以及查询重构,可以显著改善查询性能,避免超时问题。

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

标签:code,查询,material,factory,过多,version,bom,超时
From: https://blog.csdn.net/Youth_Mr6/article/details/142318232

相关文章

  • 经典sql题(八)SQL 查询详细指南总结一
    SQL查询详细指南SQL(StructuredQueryLanguage)是一种用于管理和操作关系数据库的标准语言。本文将详细介绍SQL中的一些常见操作及其用法,包括DISTINCT去重、LIMIT限制、排序、开窗函数、NULL值替换、JOIN与UNION等。1.DISTINCT去重当从数据库中查询数据时,可能......
  • 经典sql题(九)SQL 查询详细指南总结二
    示例综合上一章内容,编写一个示例SQL查询:SELECTDISTINCTa.user_id,COALESCE(b.amount,0)ASamountFROMusersaLEFTJOINtransactionsbONa.user_id=b.user_idWHEREa.status='active'GROUPBYa.user_idHAVINGCOUNT(b.transaction_id)>0ORDERBYa......
  • Springboot汉中公交线路查询系统的设计与实现7ts02
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、研究背景随着汉中市城市化进程的加速,公共交通在市民生活中扮演着越来越重要的角色。然而,现有的公交线路查询方式多依赖于纸质地图或传统查询系......
  • el-select组件:选择某个选项触发查询
    在使用ElementUI的el-select组件时,我们经常需要在用户选择某个选项后执行一些操作,比如发送请求以查询新的数据。这种场景下,我们通常会利用el-select的@change事件来监听选项的变化,并在事件处理函数中执行我们需要的逻辑。以下是一个简单的示例,展示了如何在el-select选择变化......
  • 响应式CSS 媒体查询——WEB开发系列39
    CSS媒体查询(MediaQueries)是响应式设计中的核心技术之一,帮助我们在不同设备上展示不同的样式。通过媒体查询,开发者可以检测用户设备的特性,如屏幕宽度、高度、分辨率、方向等,针对性地调整网页布局。一、CSS媒体查询基础1.1什么是媒体查询?媒体查询是CSS3引入的一种技术,允许开发者根......
  • SQL Server_查询某一数据库中的所有表的内容
    1.查看所有表的表名要列出CrabFarmDB数据库中的所有表(名),可以使用以下SQL语句:USECrabFarmDB;--切换到目标数据库GOSELECTTABLE_NAMEFROMINFORMATION_SCHEMA.TABLESWHERETABLE_TYPE='BASETABLE';对这段SQL脚本的解释:SELECTTABLE_NAME:这个语句的作用是......
  • MySQL 子查询全解析:执行、性能影响与优化策略
    在MySQL数据库的操作中,子查询是一个强大而又复杂的工具。今天,我们就来深入探讨MySQL如何执行子查询、其性能影响、优化方法以及哪些情况下应避免使用子查询。一、MySQL如何执行子查询非相关子查询非相关子查询也被称为独立子查询,它可以独立于外部查询进行执行。MySQL通......
  • SQL:查询每个类别最新的5条记录
    场景根据主表的ID列表,查询各个ID的text_comment中最新的5条记录。table:text_commentfield:text_uuidben发布于博客园v1:使用unionselect*from(select*fromtext_commentwheretext_uuid='cf6fd43a5f0527840fcdbbad55ce44c5'orderbycreate_timedesclim......
  • 【MySQL】基础部分——DDL,DML,DQL,DCL,函数,约数,多表查询,事务
    个人学习记录,供以后回顾和复习ubuntu下安装使用1.DDL,DML,DQL,DCLDDL数据库表DML增改删DQL条件查询分组查询排序查询分页查询DCL管理用户权限控制2.函数字符串函数数值函数日期函数流程函数3.约束4.多表查询多表关系内连接外连接自连接联合查询union子查询标量子查询......
  • mysql笔记8(多表查询)
    文章目录1.union联合查询可能会用到去重操作2.innerjoin内连接3.leftjoin左连接4.rightjoin右连接5.crossjoin交叉连接6.naturaljoin自然连接naturalleftjoin自然左连接naturalrightjoin自然右连接自然连接的两张表没有同名字段怎么办?7.using......