【摘要】
保險行業計算車險往年保單,須要按照車輛 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語句便可實現。