苍穹定制化开发性能优化专题系列(七)-定制化开发代码优化案例原创
金蝶云社区-云社区用户71598104
云社区用户71598104
22人赞赏了该文章 3,956次浏览 未经作者许可,禁止转载编辑于2022年06月22日 15:24:55
summary-icon摘要由AI智能服务提供

本文讨论了定制化开发中性能优化的方法和案例。首先概述了通过traceId等手段排查性能问题的常见方法,并指出某些性能问题可能由SQL执行次数过多而非单次耗时引起。随后通过三个案例详细分析了性能瓶颈和优化方法,包括通过批量查询减少数据库访问、优化大数据处理中的循环嵌套和变量计算、采用懒汉式加载优化树形控件等,旨在提升苍穹定制化开发中的代码效率。

前言

前几期定制化开发性能优化系列,我们分享了如何在生产环境中排查性能问题,如:通过traceId定位耗时模块,通过慢SQL定位耗时SQL,通过线程堆栈定位耗时代码,通过调用链跟踪梳理服务调用逻辑,通过监控指标分析资源影响(堆内存,CPU负载......)等。然而,不是所有性能问题都会体现在慢sql日志上,有些场景,单次数据库或redis操作耗时很小,但系统整体响应却很慢。这种情况可能不是有慢SQL,而是某些SQL被执行了次数过多,这时候就需要分析业务逻辑,优化代码解决问题。本文通过一些定制化开发中常见的代码优化场景和案例,为苍穹定制化开发代码提供一些优化思路。

2案例分析

2.1案例一

1. 背景:某项目生产环境,F7操作全选数据后(数据约2w+条),需对所选数据处理并返回上级页面展示(每条数据先做关联表单查询,并构造单据体和子单据体等数据,再插入上级页面对应的单据中),该操作耗时约为8.3分钟,且最

终页面返回504。

部分关键代码如下图:

在回调closedCallBack中对选中的数据进行处理

图片2.png

图片3.png

图片4.png

2. 代码分析:可以发现,代码中存在多层循环嵌套,循环体中又包含数据库查询,并且对大数据包循环使用了model.setValue更改数据,以及变量重复计算。

3. 优化方式

(1)for循环查询数据库 

图片5.png

将多次查询优化为一次批量查询,在最外层的循环中将id添加至list集合,查询时直接以list作为过滤条件,采用in的方式,保证只查询一次数据库。

(2)对大的数据包(>100)循环使用model.setValue更改数据

参考《苍穹定制化开发规范》:

禁止对大的数据包(>100)循环使用model.setValue更改数据;对大量数据对象修改,可以循环对象使用property.setValueFast设置值,最后统一updateView如:

for(DynamicProperty property : treeEntryProperties) {

property.setValueFast(newObj, objProps.get(property.getName()).getValueFast(obj));

}

this.getView().updateView(entryKey);  //针对修改数据局部刷新

(3)变量重复计算

图片6.png

代码中存在多处对集合长度重复计算,将for循环优化成:

for (int i = 0, n = list.size(); i < n; i++) {}

此写法可避免每次计算数组或者集合的大小 ,且变量作用域遵循最小范围原则。

(4)for循环嵌套

对于循环嵌套,优化的思路是减少循环次数,尽可能将执行次数由i*j*k优化成i+j+k。该代码中,内层循环中子单据体的数据封装需用到父单据体中的数据,可将父单据体数据在外层中使用Map存储,key值为与子单据体关联的标识(如:单据id),value值为父单据体的数据,或直接将list转换成Map,在内层循环封装子单据体数据时再根据key值进行匹配获取数据。

4. 总结

开发中循环语句是高频次代码,若设计不合理,容易出现多层循环嵌套进行数据对比或封装,循环中操作数据库,重复执行等情况。当数据量较大时,这类写法易引发性能问题。

除上述案例涉及问题之外,循环嵌套还需关注以下要点:

(1)避免在循环中调用服务

(2)多层嵌套循环应该遵循“外小内大”原则;

(3)提取与循环无关的逻辑至外层;

(4)捕获异常易产生额外系统开销,应避免在循环中捕获异常。

2.2案例二

1. 背景:某项目在定时任务中做大数据量查询并更新操作(查询:400w+记录,更新:200w+记录),耗时较长,部分关键代码如下:

图片7.png

2. 代码分析:原始代码中,对大数据采用分批次更新,设计合理。但代码实现上,只在数据更新操作时做了分批处理,查询获取更新数据源时未采用批量查询,依然存在循环遍历数据库,查询未使用索引等不当之处。

3. 优化方式

(1)分批处理:原始代码中分批实现如下图,首先,建议先在最外层循环将待更新的数据分批(定义分批次数及每次更新数据量),其次,将现有BusinessDataServiceHelper.loadSingle查询单条数据改为使用BusinessDataServiceHelper.load查询批量数据,并返回Object数组(此做法可以避免循环中遍历查询),再以Object数组为更新参数批量更新。

 

图片8.png

(2)查询优化:确保所有查询执行计划最优,原始代码存在几处查询未建索引(索引优化参考:定制化开发性能优化专题系列(一))。

图片9.png

(3)业务逻辑优化:如下图,判断条件可通过前面的查询过滤条件排除,从而减少循环判断的次数,获取的结果可以做分批处理,减少不必要逻辑处理代码。

图片10.png

4. 总结:

针对苍穹定制化开发大数据量操作,如更新、插入,目前建议方案分批处理,分批更新或插入。需要注意以下几点:

(1)对于查询或更新,审核等如果有批量处理的方法或接口,必须使用批量处理方法和接口,批量处理可避免在循环中做大量访问数据库或业务逻辑处理,从而提高效率。

(2)事务问题:分批次更新或插入需要考虑事务问题,如果是单一数据库操作,有事务保证要求,则需将多批次操作并入同一事务中,若是跨库跨服务操作,则需使用MQ/TCC保证事务。

2.3案例三

1. 背景:动态表单中构造树形控件,使用递归实现,加载慢,关键代码如图:

图片11.png

2. 优化分析:原始代码采用一次性加载树形控件所有数据,有两点性能问题,一是一次性加载初始化树的时间较长,二是递归加载所有数据,会在循环中多次查询数据库(虽然单次查询不耗时)。

3. 优化方式

1)对于树形层级信息展示,一般优化建议是优先考虑懒汉式加载的方式,即首次只加载第一层的信息,用户点击展开层级时,再加载其它数据,这样能减少数据库访问压力;

2)如树形控件加载数据量在可控范围内,且业务要求必须一次性加载所有数据,因为该场景中数据仅是展示,建议将获取数据的方式优化为loadSingleFromCache即从缓存获取数据。


图标赞 22
22人点赞
还没有人点赞,快来当第一个点赞的人吧!
图标打赏
0人打赏
还没有人打赏,快来当第一个打赏的人吧!