My Life My Dream!

守信 求实 好学 力行
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

问题描述:

       之前有信创项目反馈了一个批量导入的性能问题,400条数据需要3分钟,5000条需要20分钟以上,系统初始需要导入的各类数据有上百万。

原因分析:

       联系项目导入20~30条数据,并启用程序跟踪收集性能数据发现,有大量对字典表的SQL查询。

       与功能开发同事沟通确认,该SQL是导入模板中有几个关联字段,用户录入的是编码或名称,后台需要根据编号或名称查询对应的Id值,目前是简单粗暴的循环处理。

       原因倒是很清楚,如果只是为了减少对关联字段的查询次数,可以直接一次将关联数据全部加载做缓存处理,前提是关联数据的全量数据不能太大,否则容易导致OMM。

优化方案:

       按列汇总所有数据并去重,建立当前关联列数据的Map<name, id>缓存,分批次去DB检索数据,并存入Map缓存:

       1、取出一个批次的数据 names(n=names.size(),  n<=500)

       2、检查Map缓存中是否已存在

            2.1、如果Map已缓存,就根据map直接标记检查结果,并从names移除该数据

            2.2、如果n<300,则再次执行第1步骤,直至n=300或当前列所有数据均已被处理

       3、select id, name from helpdata where name in (names);

       4、将name->id作为键值对加入map

       5、将names 与 nameList的差集,按 name –> null 加入map

       6、重新执行第1步骤,直至当前列所有数据被处理完毕

 

       此方案基于一个这样的事实:一次导入或处理的数据是相对有限的(一般而言,<=10w最多也不会超过50w,再多也是分成多个批次处理),而这一个批次中某列的关联数据有可能是百万乃至千万的量级。

       如果按行循环处理,需要循环rowNum * columnNum次,以10w行、20列引用字段为例,需要执行200w次SQL查询请求;

       如果一次将关联帮助数据全部加载至内存,则对于项目、往来等可能存在很大数据量的场景,会直接导致应用OOM;

       按照本方案,理论上最坏的情况也仅执行4k次SQL查询请求(10w行、20列),而真实场景是一次处理的数据中关联列字段值重复概率非常高,实际执行的SQL请求和内存占用远低于理论的最大值。

效果验证:

       产品功能按此方案优化,部署到项目后反馈良好:内存占用稳定,1k数据(2个关联列)处理响应时间从之前的3分钟提升至10秒左右。

       image