• 那些年我们爬过的山


    【原创作品,转载请注明出处】

    写这篇文章之前想着给这篇博客起一个文艺一点的标题,思来想去,想到了那些年我们爬过的山,或者我们一起趟过的河?代码不规范,同事两行泪,这是多么痛的领悟啊!

    背景

    本组一名实习生,由于学校有事情需要回去处理,我便将他的代码接管过来,正好赶上本次迭代上线,需要将同事的代码提交测试,如果被测试出来有bug,我就来负责bug修复,代码优化等。由于不同的开发人员都有自己的编程习惯,所以不同人员所写的代码多少都会有些差异,比如:变量的命名、代码的格式等,即使组内有一套开发规范也还是会出现一些差异,这就是所谓的个性?特点? 当然 特点只是特点,并不能称得上个性。既然风格不同,那么看别人代码的时候会有值得学习的地方,也有自己感觉不舒服的地方。也罢,毕竟人都有“个性”。

    问题现象

    这次我接手同事的代码主要是一个Excel导入数据库这样一个功能,在测试人员测试导入数据的时候,使用的导入数据有2w条作为测试数据导入数据库,现象是 导入后发现页面先是显示上传中,之后页面没有任何反馈,经排查后发现是因为后台还在处理导入数据逻辑,时间过长页面没有得到反馈,导致页面超时。

    排查过程

    • 接着查看了导入逻辑,发现导入的时候,使用的是将2w条数据循环导入数据库,每一次导入都会进行数据库交互,导致数据库连接池的连接被耗尽,会再次进行创建、分配、释放等操作,从而导致之后的处理变得缓慢。
    • 基于这些考虑使用了批量导入,批量导入的目的就是一次导入多条数据,减少和数据库交互次数,减轻数据库压力。
    • 修改程序后测试导入所用的时间并不理想,继续排查发现 在导入前还有判断卡密是否存在数据库 这样一个操作,和循环插入没有什么区别,也是会循环2w次数据库查询。因此首先想到的是减少和数据库的交互次数,先把数据查询出来,放到内存中,再将两个集合进行比较,发现数据量小的时候还可以,数据量大了就会很缓慢,尝试了几个网上搜到的“高效”list去重,发现效果并不明显。
    • 遂继续找其他的方法,最后找到使用MySQL的ignore关键字【作用:若有导致unique key 冲突的记录,则该条记录不会被插入到数据库中,去重字段一定要是唯一索引】就可以解决判断卡密重复的问题,** 经过测试 导入2w条数据由原来的16分钟,减少到目前的10秒 左右** 。

    总结

    对于本次问题的排查,可以总结为问题发现,和问题排查。发现问题时首先要快速了解该功能的主要逻辑是什么,这次的导入主要有两点,一是 导入前判断是否有和数据库重复,二是导入操作。弄明白主要逻辑之后,就要分析,导入慢肯定是这两个逻辑的某一个逻辑慢,或者是两个逻辑都慢。经过分析发现,这两个逻辑都出现了循环建立数据库连接的问题,发现了问题的根本原因后,就要减少建立数据库连接次数,问题便得到解决。

    主要代码

    对于导入前和数据库判断是否有重复的,使用了mysql的一个关键字ignore,关键字的作用是:若有导致unique key 冲突的记录,则该条记录不会被插入到数据库中,去重字段一定要是唯一索引。其他就是拼接SQL使用批量导入。下面将主要的代码贴出来供大家参考。

    • 批量导入时每次批量插入100条数据,这样数据库连接建立的次数就从原来的 20000次减少到200次。
    // 批量入库,每次批量插入100条
    List<Detail> detailList = new ArrayList<>(); 
    List<List<Detail>> tempList = new ArrayList<>();
        int insertCount = 100;
        for (int i = 0; i < detailList.size(); i += insertCount) {
            if ((i + insertCount) < detailList.size()) {
                List<Detail> newList = new ArrayList<>();
                newList.addAll(detailList.subList(i, i + insertCount));
                tempList.add(newList);
            } else {
                List<Detail> newList = new ArrayList<>();
                newList.addAll(detailList.subList(i, detailList.size()));
                tempList.add(newList);
            }
        }
    
     public void batchSave(List<Detail> insertList) {
            if (CollectionUtil.isEmpty(insertList)) {
                return;
            }
            int saveSuccCount = 0;
            int saveFailCount = 0;
            List<List<Detail>> commInvoiceList = processCommissionInvoiceList(insertList);
            if (CollectionUtil.isNotEmpty(commInvoiceList)) {
                for (List<Detail> commissionInvoiceList : commInvoiceList) {
                    int count = mapper.batchSaveCommissionInvoice(commissionInvoiceList);
                    if (count > 0) {
                        saveSuccCount += count;
                    } else {
                        saveFailCount += count;
                    }
                }
            }
        }
    
    
     private List<List<Detail>> processCommissionInvoiceList(List<Detail> commissionInvoiceList) {
            List<List<Detail>> commInvoiceList = new ArrayList<>();
            int insertCount = 100;
            for (int i = 0; i < commissionInvoiceList.size(); i += insertCount) {
                if ((i + insertCount) < commissionInvoiceList.size()) {
                    List<Detail> newList = new ArrayList<>(commissionInvoiceList.subList(i, i + insertCount));
                    commInvoiceList.add(newList);
                } else {
                    List<Detail> newList = new ArrayList<>(commissionInvoiceList.subList(i, commissionInvoiceList.size()));
                    commInvoiceList.add(newList);
                }
            }
            return commInvoiceList;
        }
    
    
    • mybatis的写法
    // 批量插入数据示例
    <insert id="batchInsertDetail" parameterType="java.util.List">
    	  insert ignore into my_table
    		(id,code,status)
    	  values
    		<foreach collection ="list" item="detail" index= "index" separator =",">
    			(#{detail.id,jdbcType=BIGINT},#{detail.code,jdbcType=VARCHAR},#{detail.status,jdbcType=TINYINT})
    		</foreach >
    </insert>
    
    // 批量更新数据示例
    <update id="updateBatch"  parameterType="java.util.List">  
        <foreach collection="list" item="item" index="index" open="" close="" separator=";">
            update course
            <set>
                name=${item.name}
            </set>
            where id = ${item.id}
        </foreach>      
    </update>
    
  • 相关阅读:
    hadoop的50070端口不能访问网页的解决方法
    通过命令来查看NameNode的状态(是Active还是Standby)
    Linux 远程复制scp
    Centos 7 查看使用YUM 安装的JDK路径并配置JAVA_HOME
    【Centos7】hostnamectl 设置主机名
    centos7设置静态IP地址
    WPF窗体动态效果
    C#使用阿里云OSS
    关于WPF中的XAML
    解决Windows10关闭UAC后,开机启动项不生效的问题
  • 原文地址:https://www.cnblogs.com/luao/p/10503345.html
Copyright © 2020-2023  润新知