【摘要】
保险行业计算车险往年保单,须要按照车辆 vin 码、车架号、牌照种类和牌照号等多字段关联,涉及到几千万甚至上亿的大表,用存储过程计算很是耗时。点击车险往年保单关联计算的性能优化,去乾学院看看集算器如何把几个小时的计算缩短到十几分钟!
程序员
保险行业中,每每须要根据往年保单来快速计算和生成当年新的保单。以车险为例,在提醒老客户续保时就须要计算指定时间段的往年保单,例如某省级公司须要按期计算特定月分内可续保保单对应的历史保单。而目前在大多数保险营运系统中,这类批量数据处理任务都是由存储过程实现的,其中存在的典型问题就是存储过程性能差,运行时间长。若是只是计算一天的历史保单,运行时间尚可接受;若是时间跨度较大,运行时间就会长的没法忍受,基本就变成不可能完成的任务了。算法
下面咱们将针对这种基于历史保单信息的计算任务的性能优化。实际业务中遇到的真实的存储过程很长,有2000多行。咱们这里对问题进行了简化,只分析主体的部分,进而讨论集算器SPL语言优化相似计算的方法和思路。数据库
这个场景中计算用到的数据表包括:保单表和保单-车辆明细表。对于较大的省份,保单表和保单-车辆明细表都有几千万数据存量,天天新增保单的增量数据有一到两万条。缓存
通过简化的两个表结构以下:性能优化
保单表工具
policyid char(22) not null ,-- 保单编码(主键)性能
policyno char(22),-- 保单号测试
startdate datetime year to second,-- 开始日期 大数据
enddate datetime year to second-- 结束日期优化
保单 - 车辆明细表
policyid char(22) not null , -- 保单编码(主键)
itemid decimal(8,0) not null ,-- 明细编码(主键)
licensenoid varchar(20),-- 牌照编码
licensetype char(3),-- 牌照种类
vinid varchar(18),--vin 编码
frameid varchar(30),-- 车架编码
新旧保单的对照表:
policyid char(22) not null , -- 保单编码
oldpolicyid char(22)—上年保单编码
往年保单的计算输入参数是起始日期(istart)和结束日期(iend),计算目标是新旧保单的对照表,找不到旧保单的将被舍弃。
计算过程简化描述以下:
一、 从保单表中,找出开始日期在指定时间段(istart和iend之间)内的新增保单。
二、 用新增保单关联上一年的历史保单。关联的条件是:vin编码相同;或者车架编码相同;或者牌照种类、牌照编码同时相同。同时,要去掉旧的保险单号为null或者空字符串的数据,去掉新旧保险单相同的数据。
三、 在全部旧保险单中找到和新保单结束日期在90天以内的,就是上年保单。
一、 理解业务,采用更好的算法,而不是照搬存储过程。
存储过程若是遇到了很难优化的性能问题,根本缘由多是采用的计算方法出了问题。这每每是由于SQL原理和模型形成的,要靠新的工具经过支持更好的计算方法来解决。若是用SPL简单翻译存储过程的语句,计算方法没有改变,性能也很难提高。
推荐的作法是经过存储过程理解业务的需求,而后从原理层面思考更快的算法,在工具层面采用集算器SPL提供的更优化的算法从新实现。
乾学院提供了不少性能优化的案例,能够帮助SPL程序员快速找到更好的计算方法。
二、 数据外置,利用集算器得到更好性能。
集算器提供了私有数据文件格式,具有压缩、列存、有序等有利于性能的特色。所以能够将数据库中的数据预先缓存到集算器数据文件中,利用数据外置优化总体性能。
三、 针对对关联计算,区别分类加以优化。
和SQL的关联计算不一样,集算器中可以对不一样类型的join采用不一样的算法,包括主键相同的同维表、外键表、主子表、大表关联小表等等细分状况。而若是出现了两个大表cross join的状况,则有必要从新分析业务需求。
一、 从数据库中导出保单表和保单车辆明细表。按照policyid排序以后,存放到组表文件POLICY.ctx和POLICY_CAR.ctx中。这里的排序很重要,是后续实现有序归并的前提条件。由于数据库JDBC性能较差,因此第一次导出所有历史数据的时候速度会比较慢。可是之后天天导出新增数据,增量更新组表文件就很快了。
二、 针对POLICY.ctx的enddate字段新建索引index_enddate。
三、 数据准备的具体代码能够参考教程的组表部分。
通过分析、测试发现,原存储过程性能优化的关键在于四个关联计算。首先是新增保单和保单-车辆明细表经过policyid关联来得到车辆信息,以后再与保单-车辆明细表分别经过vinid、frameid、licenseid以及licensetype关联三次,来获取历史保单。一天的新增保单有1万多条,这四次关联的时间尚可忍受。而一个月的新增保单有四十多万条,这四次关联的时间就会达到1到2个小时。
对此,咱们优化这个存储过程的思路就是利用SPL的计算能力,在对两个主表一次遍历的过程当中,完成上述四个关联计算。这样,不管是一天仍是一个月的新增保单,计算时间都不会明显延长。
具体的SPL代码分为两大部分:
1、过滤出指定时间段(istart和iend之间)的新增保单数据。
一天的新增保单1到2万条,三十天的新增保单30到60万条,这个量级的数据能够直接存放在内存中。具体代码以下:
A一、B1:打开组表文件“保单表”。
A二、A3:从保单表中过滤出指定时间段(istart和iend之间)的新增保单,过滤时使用了预先生成的索引index_enddate。
A四、B4:打开组表文件“保单车辆明细表”。
A五、B5:用新增保单号,关联保单车辆表,找出车辆信息。
A六、A7:关联新增保单信息和车辆信息,生成新增保单和车辆信息表。
2、对历史保单完成三种方式的关联计算,获得新旧保单对照表。
A八、B8:打开两个组表文件,保单表和保单车辆明细表,用须要的字段创建游标。
A9:用policyid关联两个游标。如前所述,两个表都已经按照policyid排序了,因此这里的joinx是采用有序归并的方式,两个表都只须要遍历一次,复杂度较低。而SQL的HASH计算性能则只能靠运气了。关于有序归并的介绍参见【数据蒋堂】第 35 期:JOIN 提速 – 有序归并。
A10:循环取出两个组表文件关联的结果,每次取出10万条造成序表。
B10:关联结果生成新序表。其中,牌照和牌照种类用“|”合并成一个字段。
B十一、C十一、B12分别按照三种方式作内链接,计算历史保单。
C12:纵向合并三个内链接的结果。
B1三、C13:找出新保单id不等于旧保单id而且旧保单号不为空的数据,生成新序表。
B14:结果合并到B14中。
A15:过滤出结束日期大于旧保单结束日期,“旧保单结束日期”和“新保单开始日期”的间隔不超过90天的数据。
A1六、1七、B17:对新保单、旧保单去掉重复,存入结果文件。
在实际项目中,存储过程和集算器对比测试数据以下:
从结果能够看出,一样的条件下:
一、新增保单数据量越大,集算器性能提高越明显。30天新增保单计算时,性能提高6.6倍。
二、存储过程计算时间随着数据量线性增长。集算器计算时间并不会随着数据量线性增长。
三、数据量较小的时候,集算器和存储过程的计算性能都在可接受范围内;数据量较大时,存储过程须要计算几个小时,集算器的计算时间仍在十几分钟。
四、在此次测试中,没有对保单表和保单车辆明细表建索引,计算过程当中集算器须要对两个表作遍历查找。所以,数据量较小时,集算器也须要一个基本的遍历计算时间。而数据库建有索引,在小数据量时会有优点。若是集算器也建有索引,这个场景也能够再优化。但因为目前的指标已经能够达到实用,而用户方更关心的是大数据量场景,因此没有再作进一步的优化测试。
集算器采用压缩列存的方式保存数据。保险单表有70个字段,参与计算的只有十几个字段;保险单明细表有56个字段,参与计算字段不到十个。所以,采用列存方式对性能的提升效果较好。
保险单表和保险单明细表存成集算器组表文件,压缩后只有3G多,也能够有效提升计算速度。
集算器数据文件中的数据按照保险单号有序存放。保险单表和保险单明细表按照保险单号关联的时候,能够有序分段关联,速度提高明显。
计算中间结果也是有序的,无需再从新建索引,有效节约了原来存储过程当中建索引的时间。
用新保单去找上年、上上年和上三年的保单,须要按照vin码、车架号或者牌照加牌照种类三种方式来判断是否同一辆车。
原存储过程是用新保单表去和保单表、保单明细表屡次关联,计算的时间会随着新保单表的数据量而线性增加。
而在集算器中采起的方式是:保单表和保单明细表有序关联以后,循环分批取出(好比:每次取10万条)。在内存中,每一批数据都和新保单经过三种方式关联。循环结束,三种方式的关联也都完成了。这样就实现了大表遍历一遍,同时完成三种方式的关联计算。
对于存储过程来讲,没法实现这种算法的根本缘由是:一、没法有序关联两个大表;二、用临时表不能保证全内存计算。
SPL语言代码更短,调试更简单,能够有效提升开发效率。
原存储过程的完整代码约1800多行,用SPL改写后,仅约500格SPL语句便可实现。