zoukankan      html  css  js  c++  java
  • 【优化】记录一次方法性能优化

    背景:

    程序总体设计:

    系统先从数据库 A 表中取出数据(步骤 1),放入 B 库中的表里(步骤 2);

    在从 B库中的表里取出数据(步骤 3),经过层层的规则校验,再放回 B 库中的表中;

    速度优化原因:

    原本:方法使用单线程的方式;校验产品是否自动化通过远程调用的方式;每次批处理调价数据是50条;数据极慢;
    如:202109273版本,开始时间是:2021-09-27 18:21:04,处理完是:2021-09-27 18:38:33;数据量是:477853;用时:17分钟
    如:202109173版本,开始时间是:2021-09-17 15:41:54,处理完是:2021-09-17 16:36:07;数据量是:914366;用时:55分钟
    如:202109161版本,开始时间是:2021-09-16 10:02:59,处理完是:2021-09-16 10:15:44;数据量是:411208;用时:12分钟
    如:202109151版本,开始时间是:2021-09-15 11:24:25,处理完是:2021-09-15 11:37:55;数据量是:413146;用时:13分钟
    40万条数据需要12—13分钟,90万数据需要55分钟,太慢了.....

    优化目标:
    100万数据量希望将规则校验时间压缩在10分钟以内;

    优化方向一:

      每次批处理调价数据量增加到1000条;
      “校验产品是否自动化”由远程调用接口,改为直接查询数据库;(远程接口调用会在网络上限制系统的性能)
      规则校验采用多线程处理,对老代码适配多线程;

    成果:

      收效甚微!!!!,由此可见,速度慢的问题不知规则过滤导致的;

    优化方向二:

      排查慢的原因,发现是 步骤 1和步骤 3 很慢,发现每次查询都是 1 秒以上,时间占据了总时间的 70%,sql:

    select 
        id,
        .....,
        del_flag
    from ol_autopricing_auto_pricing_bi_data
    where data_version = #{dataVersion}
        and del_flag = 0
    order by id
        limit #{start},#{range}

    结论就是:limit 在大数据了下分页很慢,淦,中招了

    优化步骤 1:

    换掉步骤一的 sql :

    业务上条件:数据都是连续的,不会出现数据分布杂乱的情况

    方案:不在使用 limit 分页,而使用主键id分页

    查询出主键分页的起始和结束位置:

    select min(id) as minId, max(id) as maxId
    from ol_autopricing_auto_pricing_bi_data
    where data_version = #{dataVersion} and del_flag = 0

    根据起始和结束位置对主键进行分页:

    起始位置:start=manId,range=1000

    循环查找时通过程序控制 (start+range) <= maxId ,限制 id 不会越界;

    select 
        id,
        .....,
        del_flag
    from ol_autopricing_auto_pricing_bi_data
    where id between #{start} and #{end}and data_version = #{dataVersion}
        and del_flag = 0
    order by id

    之后发现数据很快,快到飞起,查询速度都是在 10 毫秒以内

    优化步骤 2

    插入的方案一般有两种方案:

    1. 用一个 for 循环,把数据一条一条的插入(这种需要开启批处理)。
    2. 生成一条插入 sql,类似这种 insert into user(username,address) values('aa','bb'),('cc','dd')...

    先说第一种方案,就是用 for 循环循环插入:

    • 这种方案的优势在于,JDBC 中的 PreparedStatement 有预编译功能,预编译之后会缓存起来,后面的 SQL 执行会比较快并且 JDBC 可以开启批处理,这个批处理执行非常给力。
    • 劣势在于,很多时候我们的 SQL 服务器和应用服务器可能并不是同一台,所以必须要考虑网络 IO,如果网络 IO 比较费时间的话,那么可能会拖慢 SQL 执行的速度。
    • 虽然是一条一条的插入,但是我们要开启批处理模式(BATCH),这样前前后后就只用这一个 SqlSession,如果不采用批处理模式,反反复复的获取 Connection 以及释放 Connection 会耗费大量时间,效率奇低,

    再来说第二种方案,就是生成一条 SQL 插入:

    • 这种方案的优势在于只有一次网络 IO,即使分片处理也只是数次网络 IO,所以这种方案不会在网络 IO 上花费太多时间。
    • 当然这种方案有好几个劣势,一是 SQL 太长了,甚至可能需要分片后批量处理;二是无法充分发挥 PreparedStatement 预编译的优势,SQL 要重新解析且无法复用;三是最终生成的 SQL 太长了,数据库管理器解析这么长的 SQL 也需要时间。
    • 当数据量大的时候,生成的 SQL 将特别的长,MySQL 可能一次性处理不了这么大的 SQL,这个时候就需要修改 MySQL 的配置或者对待插入的数据进行分片处理了,这些操作又会导致插入时间更长。

    使用第一种方式:
    打开了数据库配置mysql 的数据库配置 rewriteBatchedStatements=true,那么就可以选择使用批量插入,一次提交的方案:

        /**
         * 批量保存的方法 获取一个 session ,通过一个 Session 提交保存
         * @param entityDates
         */
        public void addEntityOneByOne(List<AutoPricingAffairItemEntity> entityDates) {
            SqlSession sqlSession = pcsSqlSessionFactory.openSession(ExecutorType.BATCH);
            PcsDatasourceMapper mapper = sqlSession.getMapper(PcsDatasourceMapper.class);
            for (AutoPricingAffairItemEntity entity : entityDates) {
                mapper.insertBatchBiData(entity);
            }
            // 如果不关闭,11 次之后就会获取不到链接
            sqlSession.commit(); //记得提交
            sqlSession.close(); //记得关闭
        }

    优化步骤 3

    也是查询,该表更大,但是由于该表是业务表,数据可能比较乱,主键分页不太实用,所以使用join的方式:

    select 
        bbb.id,
        ....
        bbb.del_flag
    from
    (select id
    from pcs.ol_autopricing_auto_pricing_bi_data
    <where>
        del_flag = 0
        <if test="null != dataVersion and '' != dataVersion">
            and data_version = #{dataVersion}
        </if>
    </where>
    order by id
    <if test="start != null and range != null and range > 0">
        limit #{start},#{range}
    </if>
    ) aaa
    inner join pcs.ol_autopricing_auto_pricing_bi_data bbb
    on aaa.id = bbb.id

    先通过子查询,查询出主键,在通过结果集形成临时表,用小表驱动大表的方式,通过主键 join 出我们的全部字段,实测50 万数据,只需要 120 毫秒,能接受了

    通过这两个的优化速度已经提升了近 85% 了, 剩余的优化都是开多线程提升了,确实没什么优化的空间了;

    心得:

      后来也进行了综合对比,发现切换多线程和不切换多线程的时间提升不明显,性能慢的主要原因还是出现在查询数据库上;

      很少说程序写的差导致总体时间过长的问题;

      后续排查性能慢的首选还是得先排查慢的 sql ;

      分页查询的方式有很多,具体使用哪一种,还得看具体的业务场景,有的性能可能更高,但是并不适用;例如:优化步骤 3 时,步骤一优化方案也可以用,但是会查出很多别的数据,还要系统做判断,既增加了 io,又增加了程序的复杂度,得不偿失;

  • 相关阅读:
    常规排序算法 : 冒泡排序
    console.log(([])?true:false); console.log(([]==false?true:false)); console.log(({}==false)?true:false)
    近况
    正向代理和反向代理
    Angular项目目录结构
    模块化Vs组件化
    模块化开发——高内聚低耦合
    20190608笔试题のCSS-属性继承
    20190527-JavaScriptの打怪升级旅行 { 语句 [ 声明 ,变量 ] }
    20190430-Bootstrapの组件
  • 原文地址:https://www.cnblogs.com/lzghyh/p/15428204.html
Copyright © 2011-2022 走看看