PostgreSQL——51風控系統背後的利器

danny_2018發表於2018-08-16

【IT168 專稿】本文根據張澤鵬老師在2018年5月12日【第九屆中國資料庫技術大會】現場演講內容整理而成。

講師簡介:

張澤鵬,51信用卡架構師。前道富資訊科技(浙江)有限公司技術專家,參與多個金融相關的系統開發,後自主創業。2015年加入51信用卡管家擔任架構師,負責風控、信審、資料支援等互金相關係統的設計與開發。從2012年開始使用PostgreSQL,擁有多年PG相關的開發和實踐經驗。

摘要

51信用卡管家作為網際網路金融領域的獨角獸企業,在風險控制上積累了多年經驗。PostgreSQL豐富的特性給風控的分析與開發工作帶來極大的便利,並支撐了51實時的風控業務。本文將介紹PostgreSQL在51風控系統中的應用,並結合4個場景介紹實踐經驗,以及各自方案的演進過程。

正文:
風控業務概述

風控,顧名思義,就是風險控制。風控業務人員需要在諸多樣本里挑出有風險的樣本,並把它們排除掉。就像漫畫中展示的一樣:找出那個不一樣的傢伙!

 

所以,風控工作很像在玩“大家來找茬”遊戲:把左邊的圖看成是正樣本,右邊的圖看成是負樣本,負樣本會盡可能偽裝得與正樣本很像。風控業務人員利用他們的火眼金睛,提煉出特徵與規則,辨別正負樣本。

 


以此圖為例,風控業務人員可能會注意到“狐狸耳朵數量”這個特徵,並提出“正常狐狸有兩隻耳朵”的規則。未來再遇到狐狸影像,就能用這條規則來快速辨別真偽。

類比到金融風控領域,風控業務人員也會提出類似的規則。例如:符合以下條件的申請予以透過,否則拒絕:
 男性客戶:年齡在22到55歲之間;
 女性客戶:年齡在18到65歲之間。
風控系統概述
有了風控規則,就可以線下聯絡申貸客戶收集資訊,並人工判斷是否符合規則。但純人工的方式效率低下、容易出錯,隨著業務規模的增長,顯然需要一套自動化的風控系統來代替人工方式。如果說風控規則決定了能不能做互金這個生意,那風控系統就決定了生意能做多大!

為實現上述風控規則,需要獲得客戶的“年齡”與“性別”兩個特徵變數;而“年齡”能由“出生年份”計算得出;“出生年份”與“性別”又能從“身份證號”中獲得。這意味著,只要提供一個“身份證號”作為輸入,就能依次獲得“出生年份”、“性別”,進而得到“年齡”,最後得到風控規則的結果。

 

舉個例子,有一個身份證號:330106199011110119(此為虛擬身份證號,僅供本文示例用途);按照18位身份證號的規則,首先提取第7到10位的出生年份“1990”;接著提取倒數第二位“1”,是一個奇數,說明性別是男性;然後,與今年的年份“2018”相差,得出年齡是2018-1990=28歲;最後,確認年齡28歲在男性客戶的區間範圍內,即最終結果是“透過”!
 


這就是風控系統的工作模式:不停地用一些變數算出另一些變數,直至獲得最終的結果。日常辦公軟體中,電子表格(如Excel)正是一款符合該工作模式的軟體:使用公式,單元格可引用另一個單元格內的資料,當被引用的單元格資料發生變化時,公式的計算結果會自動更新。正如前文所言,一些業務剛起步的互金公司,不值得開發一套自主的風控系統,就可以用Excel來完成風控工作。將上述規則對映到Excel中,對應下圖中的5行記錄。除第一行的身份證號,其他值都直接或間接由身份證號衍生而來。

 
上圖中最後一個特徵變數的公式用到了IF、OR與AND三個函式,並且依賴了B4和B5兩個單元格。Excel預設已提供了豐富的函式,但某些複雜運算邏輯——例如求熵——並未提供,但Excel支援用VBA自定義函式。下圖中定義了一個計算階乘的函式,無需重啟就能直接在公式中使用。雖說Excel不太招DBA們待見,處於鄙視鏈的底端,但擴充套件性和易用性方面竊以為能碾壓不少主流的“真正”資料庫。
 
從功能上來講,51的風控系統和Excel是一樣的,都提供了儲存能力與計算能力。下圖展示了51風控系統的組成模組:其中維度等價於Excel檔案;快照等價於Excel的工作表(Sheet);而Excel裡的單元格(Cell)在風控的系統中稱做變數,並按照所屬的業務意義細分為“清洗變數”、“特徵變數”、“模型變數”、“規則變數”;變數之間也有依賴關係,任務排程模組提供了類似Excel中聯動修改單元格值的功能。

 
綜上所述,只要提供必要的輸入,風控系統就會像多米諾骨牌一樣,一個接著一個,聯動地運算起來!
快照資料
快照,故名思義,是資料在某個時間點的備份。快照功能是風控系統的基礎,在任何時刻發生了變數計算,系統都會將相應的輸入引數、中間變數、最終結果儲存成快照。這些快照資料有三類作用:
1. 歷史證據:對業務而言,無論何時訪問一筆訂單相關的資料都是相同的,資料不會隨時間變化而變化;對開發而言,方便線上問題的定位和排查。
2. 快取資料:風控資料後續還會在信審、催收等系統多次用於展示,快照資料既是證據又是快取,避免每次查詢時多次計算。
3. 建模分析:積累的快照資料後續能用於分析與建模,供機器學習等演算法使用。
資料結構
上述例子的5個變數,轉換成JSON格式後,資料結構如下:
{
    "id_card": "330106199011110119",
    "birth_year": 1990,
    "age": 28,
    "gender": 1,
    "is_accept": 1
}
如前文所述,變數會根據其業務意義分類。例如按照“輸入變數”、“中間特徵變數”、“輸出規則變數”分成三類,資料結構就變成如下更復雜的巢狀結構:
{
    "inputs": {
        "id_card": "330106199011110119"
    },
    "features": {
        "birth_year": 1990,
        "age": 28,
        "gender": 1
    },
    "rules": {
        "is_accept": 1
    }
}
在真實業務場景中,資料結構往往會更復雜、巢狀更深。例如再從身份證號中解析出省市區資訊,資料結構會變成三層巢狀結構:
{
    "inputs": {
        "id_card": "330106199011110119"
    },
    "features": {
        "birth_year": 1990,
        "age": 28,
        "gender": 1,
        "address": {
            "province": "浙江省",
            "city": "杭州市",
            "district": "西湖區"
        }
    },
    "rules": {
        "is_accept": 1
    }
}
儲存方案一:MongoDB
儲存這類複雜的巢狀JSON資料,用MongoDB這種文件型資料庫是最方便的。所以第一版的快照系統底層儲存採用MongoDB——JSON進JSON出。MongoDB對開發者很友好,但業務人員大多來自傳統銀行,他們更熟悉SAS與SQL,MongoDB之於他們而言,和儲存成JSON檔案沒區別,都很難開展統計與分析工作。
儲存方案二:MySQL
為協助業務快速前進,第二版的快照系統,將底層儲存方案換成了業務人員更熟悉的關係型資料庫——MySQL。並制定了以下規範,用於將巢狀的JSON資料,儲存到一系列二維表中:
 如果值為基礎型別(數值或字串),則代表“欄位”型別
 如果值為複合型別(物件或陣列),則代表“表”型別
 第一層必須為“表”型別。
 每一張表必須有一個唯一主鍵“id”。
 巢狀的“表”,必須有一個外來鍵欄位指向上一層表的“id”。
例如,前文的JSON資料對映成以下表結構:
 inputs:表
 id:欄位,主鍵
 id_card:欄位
 features
 id:欄位,主鍵
 birth_year:欄位
 age:欄位
 gender:欄位
 address:表
 id:欄位,主鍵
 feature_id:欄位,外來鍵,指向features.id
 province:欄位
 city:欄位
 district:欄位
 rules:表
 is_accept:欄位
將JSON拍平成二維表之後,業務人員又能愉快地使用SQL查詢並分析資料了,但卻給開發帶來一些負擔。被MongoDB慣壞之後,工程師們希望新方案能保留MongoDB直接存取JSON的能力:只要把JSON丟進來,希望系統就能自動一層一層去儲存;只要給一個ID,希望系統能自動查詢相關表,並將結構組織成JSON再返回。

為此,我們開發了一箇中介軟體——ActiveMap——對JDBC做了一層簡單的封裝:它能自動獲得資料庫的元資訊——例如表名、欄位名、外來鍵依賴等——根據這些元資訊,與JSON做自動對映,以實現JSON的自動拆箱和自動裝箱。
儲存方案三:PostgreSQL
方案二上線後只幾個月,業務爆發式增長,很快遇到了新問題:因為風控業務的特殊性,需要頻繁地新增特徵變數,即頻繁地新增欄位。但快照資料積累很快,幾個月下來平均每張表有過億的資料量,往這些大表裡新增欄位或新增索引,即使業務不停工也要折騰很久。於是,在開發快照系統的第三個版本時,再次調整了底層儲存,換成PostgreSQL。PostgreSQL擁有了兩個強大的特性:
1. 建立索引時用concurrently,就能併發地建立索引,不會取得任何能阻止該表上併發插入、更新或者刪除的鎖。
2. 新增新的列,如果不指定預設值,就只修改後設資料,能瞬間完成。
有了這兩個特性,後續新增特徵變數就不用大費周章了。
儲存方案四:PostgreSQL+JSONB
方案三上線後執行了近一年,直到某天流量爆發,平均一分鐘能收到8000多筆申請,此時的資料結構已經複雜到有5、6層巢狀結構,涉及十幾張表的寫入,最寬的表有超過400個欄位,每筆申請會瞬間產生約20萬條快照記錄,即每分鐘儲存約16億條記錄。據DBA觀察,當時TPS已經突破5萬大關。雖然應用層有重試機制,能保證所有任務最終都能正常完成,但資料庫已經不可用。

經過重新評估,最終用PostgreSQL自帶的JSONB型別代替多表結構。這彷彿回到方案一:用MongoDB存JSON。其實,在快照系統不斷升級的這兩年裡,51的大資料平臺也同樣在進步,並搭建了內部的離線數倉,線上資料會T+1同步到離線Hive倉庫,同時解析JSON資料,展開成二維表。業務人員不再直連線上業務庫,也就不關心線上庫的儲存形式。

改造後的系統,儲存效率提升了10多倍,已經能滿足目前線上的業務量。我們從中學到:架構師在做技術選型時,都是戴著腳鐐跳舞,得因地制宜,得結合環境的約束,才能給出當下最適合的方案。在方案一就採用MongoDB屬於太過激進:僅考慮線上輸出風控這一個環節,MongoDB是合適的方案;但全域性考慮上下游系統的協作,以及各方的學習成本,MongoDB就不見得是最佳選擇了。
維度資料
維度是快照資料依附的主體,即這些資料屬於“誰”。大多數金融業務系統基本只和一個維度的資料打交道。例如,信審系統只處理訂單相關的資料,清算系統只處理標的相關的資料。但風控系統會貫穿整個金融業務生命週期,例如下單前風控系統要提供使用者的授信額度和信用評級,這類資料屬於使用者維度;下單後訂單號是唯一表示,維度變成訂單維度;募資時維度又變成標的維度。這些系統在各自處理過程中,都會從風控系統查詢一些風險相關的資料,所以風控系統更像是一個橫向的切面服務,而不是垂直的服務。

不同維度的資料需要相互隔離、不能覆蓋,最簡單的做法是給每個維度的快照資料建立獨立的表。例如上述例子中,有inputs、features、rules三類快照資料,假設只有訂單維度(orders)和使用者維護(users),排列組合就產生了3*2=6張表。後續,無論是新增一個維度還是新增一個快照,都需要建立一批表,所以這種方式會引起組合爆炸!
橋接模式
設計模式中的橋接模式可以解決這種多對多組合的問題,它將抽象部分與它的實現部分分離,使兩者能獨立地變化。借鑑這個思路,將“維度”與“快照”抽象成兩個獨立的部分,透過繼承各自獨立變化。如下圖所示:
 

雖然橋接模式可以解決組合爆炸的問題,但它畢竟屬於物件導向領域,需要將諸如“繼承”等物件導向的語義轉換成“join”等關係型正規化的語義,才能用來解決上述關係型資料庫中的問題。

例如,互金領域的訂單都會包含申貸金額和期數,但結合更具體的場景,如面向個人的產品訂單,需要儲存申貸個人的身份證號等,而面向商家的產品訂單,則需要提供營業執照等資訊。用物件導向的語言設計時,通常會先定義一個抽象的訂單基類,再定義個人訂單與商家訂單兩個子類;在關係型資料庫中設計時,通常會定義一個訂單表,只包含公共的欄位,再定義個人訂單表和商家訂單表,分別儲存各自的擴充套件欄位,並都包含一個外來鍵指向訂單表的訂單主鍵。在維護舊系統的過程中,如果你經常能看到類似XXX_EXT結尾的擴充套件資訊表,基本都屬於這種模式。但這種模式讀寫都很麻煩:讀的時候,需join多張表,繼承的層次越深,join的表越多;寫的時候,需做好多表插入的事務控制。
繼承表
看起來在關係型資料庫中實現橋接模式並不容易。翻看PostgreSQL官方文件、第一章、第一節:《What is PostgreSQL?》,第一句話說:PostgreSQL是一個物件-關係型資料庫管理系統!它不可思議地內建了物件導向的繼承功能:
 子表繼承父表的欄位
 父表可查詢子表資料
 透過約束排除無關表:constraint_exclusion
 與父表共享一個序列
它完美地融合了物件導向和關係型兩種正規化,用inherits關鍵字繼承父表,就像Java中用extends關鍵字繼承父類一樣簡單。舉個例子:有兩張子表children1和children2分別繼承了parents父表,並各自宣告瞭約束。
create table parents (id serial primary key, type int, name text);
create table children1 (primary key (id), check (type = 1)) inherits (parents);
create table children2 (primary key (id), check (type = 2)) inherits (parents);
select * from parents where type = 1;
當執行第4條type=1查詢時,只會掃描parents表和children1表,並不會掃描children2表。維度資料庫正是利用該特性,實現了橋接模式,幾乎沒給開發工程師增加工作量。
名單資料
名單資料通常是指黑名單和白名單,一般都是隨著業務發展不斷積累,在風控中屬於最簡單直接的攔防方法。
方案一:資原始檔
51風控專案剛起步時,只有一份幾十萬條記錄的身份證黑名單,儲存成txt檔案才幾MB。所以,第一期直接把名單打包進專案中:
1. 名單作為資原始檔打包進Java專案中。
2. 系統啟動時將資料載入到記憶體,生成HashSet。
3. 查詢時,檢查key是否在集合中即可。
方案二:Redis
方案一簡單、粗暴、有效,但上線後其他專案也想使用這份黑名單,如果在每個專案中都複製一份,未來往黑名單中新增記錄就變得很困難。於是升級成方案二:利用Redis叢集來維護名單資料,並提供查詢服務。使用依舊很簡單,效能也很好。

方案二持續了很久,直到遇到一項新需求:提供模糊查詢功能。例如,名單庫中有“330106199011110119”這條記錄,當使用“3301061990111*****”這類帶掩碼key來查詢時,希望返回“true”。

這個需求用Redis的SCAN也可以實現,不過SCAN畢竟還是要掃一遍所有的KEYS;另一種方法是插入明文的時候,同時插入相應的掩碼,即掩碼也儲存在名單庫中,就能用掩碼查詢了。這兩種方法,都需要計算明文對應的掩碼,只不過前者在查詢時計算,後者在插入時計算。對於名單庫這種一次插入多次查詢的場景,後者會更適合。
方案三:PostgreSQL+Groovy
為了新增時生成掩碼,當時總共評估了三套方案:
1. 增強Redis:用lua或C語言改造Redis,提供一個自定義的命令(如MASK_SET)代替SET命令,自動新增掩碼key。
2. 部分取代Redis:新開一個Java工程,提供單個新增、批次新增等介面,取代直接寫Redis,但最終明文和掩碼的key依舊儲存在Redis中,讀操作直接訪問Redis。
3. 拋棄Redis:新開一個Java工程,同時提供讀寫名單的介面,完全遮蔽甚至拋棄Redis。
方案一對於應用層而言非常優雅,改動量最小,但後續新增新的掩碼規則時都要去修改並重新部署自定義命令,在當時的人才儲備下實施成本較高;方案二讓應用層接入成本升高,需同時接入Redis和服務介面,還需同時考慮兩者的可用性與降級策略等;綜合考慮之後,最終選擇方案三,實施和遷移的成本都還能接受。

考慮到掩碼規則在未來一小段時間內會頻繁地增刪改,為避免每次修改規則都重新發布,所以不能將規則硬編碼,而是用Groovy指令碼作為規則配置到資料庫中,這些指令碼稱作生成器(Generator),它們接收一個JSON作為入參,檢查引數是否擁有相關欄位,如果有則返回相應的掩碼,否則返回空。

此外,每新增一個Generator時,都需要全量計算明文Key相應的掩碼Key;刪除明文Key時,也需要關聯刪除相應的掩碼Key。因此,還要儲存Generator與Key的對映關係。原計劃新工程只是封裝Redis,但關聯查詢並非Redis所擅長,在經過一系列效能對比之後,最終決定用PostgreSQL取代Redis,設計方案如下:
   

所有明文和掩碼儲存在Keys表中。
 Keys的資料型別採用JSONB,因為不同名單Key的型別與欄位數目都有可能不同。例如有些名單是手機號碼、有些是姓名+身份證號、有些是使用者ID。
 被加入名單的原因存入reasons表。
 兩者透過事件(Events)相關聯。
 生成器儲存在Generators中。
 生成器生成的掩碼Key透過Relations與Events關聯。
方案四:PostgreSQL+UDF
方案三能滿足模糊查詢的需求,也在生產中運用了一段時間,但整個方案相當“湊活”,使用過程中遇到不少麻煩:
 每次新增Groovy Generator時,需全量計算所有明文Keys。但因為待計算的量比較大,無法一次性快速計算完,不得不分批計算,於是又額外需要任務管理與排程功能,導致系統愈加複雜。
 無法用PostgreSQL自帶的COPY命令來批次匯入Keys,因為掩碼Key依賴應用層用Groovy生成,只能呼叫應用端的介面逐個計算。
 批次匯入時會佔用應用層大量資源,導致正常查詢請求不能得到及時響應。
 因為資料庫之上架了一層應用層,整體鏈路變長,效能變差。
這些麻煩歸根到底,原因是資料與計算不在同一體系內,資料不得不在持久層和應用層之間來回傳遞;同時,因為應用層作為統一出口,諸如批次匯入資料這些原本資料庫已經支援的功能,又不得不在應用層再實現一遍,還實現的更加蹩腳。是否有可能透過增強現有的持久層體系,而不是額外引入一層新的應用層?這樣既能充分利用持久層的現有功能,又能避免資料傳輸和排程的額外工作,也避免重複造輪子。

前文提到,Redis透過模組功能支援自定義功能,Excel透過VBA也能輕鬆自定義函式。PostgreSQL中,透過UDF(User Defined Function),允許用C、SQL、PLPGSQL、Python等幾乎所有主流程式語言,動態新增自定義函式。例如,用SQL實現計算身份證號後5位掩碼的函式:
create function id_card_mask(jsonb) returns text as $$
  select substring($1->>'id_card', 1, 13) || '*****'
$$ language sql immutable;
有了計算掩碼的函式,再繫結一個觸發器(Trigger)——插入明文資料後自動再插入相應的掩碼資料。但轉念一想,掩碼真正的用途是用來查詢,例如:
select * from keys where id_card_mask(context) = '3301061990111*****';
之所以一直考慮提前計算好並儲存掩碼結果,是避免每次查詢時都反覆計算。PostgreSQL提供了另一個特性——表示式索引——既能避免重複計算,又能節約空間。索引通常都建立在真實的列上,而表示式索引允許給函式的計算結果建立索引:
create index on keys (id_card_mask(context));
當查詢條件中出現了與表示式索引中完全一致的表示式時,PostgreSQL會直接使用索引中的計算結果,並不會執行該表示式。所以,即使函式執行非常耗時,也能高效地查詢!利用UDF和表示式索引,可以完全拋棄應用層,甚至連Generators和Relations這兩張表也可丟棄,整體方案得到極大簡化。

來看一下最佳化前後的對比:最佳化前,磁碟空間佔用接近10G,最佳化後只用了2G;TPS,即單位時間生成新Key的速度,最佳化前在應用端用Groovy指令碼計算並儲存,TPS約2500,最佳化後突破了12000。效果是非常驚人!最重要的是,可以直接用COPY等命令批次匯入名單,索引能自動生成,也無需擔心有遺漏。
 

任務排程
任務排程,正如前文所述,處理任務之間的依賴關係,就像解決Excel中單元格相互引用。舉個例子:有A、B、C、D四個任務
 D依賴B
 D依賴C
 C依賴A
 B依賴A
任務排程的工作就是管理任務間的依賴關係,按照有效的順序依次執行任務。在上例中,排程系統會首先執行A、接著同時執行B和C、最後執行D。如下圖所示,任務的依賴關係是有向無環圖,用拓撲排序就能得到正確的執行順序,其思想是每次取出“出度”為0的節點,這些節點不依賴其他節點,處於可執行狀態。
 

方案一:多表join
 每個任務建立一張與之對應的任務佇列表
 每張任務表有一個status狀態欄位,可取值:
 submitted:待處理
 completed:已完成
 依賴某個任務,就join相應的任務佇列表
以上述4個任務為例,需建立queue_a、queue_b、queue_c、queue_d四張任務佇列表。結構如下:
create table queue_x (
  id bigint primary key,
  status text not null default 'submitted'
);
假設4張表中都插入一個id為1的任務,並且狀態都是submitted。根據前文的依賴關係,可以算出每個任務依賴的佇列狀態。比如任務A,不依賴其他任務,只要佇列queue_a中有一條狀態為submitted狀態的任務就能馬上執行;任務B和C都依賴了任務A,除了各自的佇列queue_b和queue_c有一條狀態為submitted的任務,還需要queue_a中有一條id相同且狀態為completed的任務;任務D,則需要queue_d有狀態為submitted,queue_b與queue_c狀態為completed。任務排程系統會根據以上依賴關係,自動生成SQL查詢語句:
select queue_a.id from queue_a where queue_a.status = 'submitted';
select queue_b.id from queue_b inner join queue_a on queue_b.id = queue_a.id where queue_b.status = 'submitted' and queue_a.status = 'completed';
select queue_c.id from queue_c inner join queue_a on queue_c.id = queue_a.id where queue_c.status = 'submitted' and queue_a.status = 'completed';
select queue_d.id from queue_d inner join queue_b on queue_d.id = queue_b.id inner join queue_c on queue_d.id = queue_c.id where queue_d.status = 'submitted' and queue_b.status = 'completed' and queue_c.status = 'completed';
1. 第一輪:queue_a的狀態變成了completed;其他佇列不變。
2. 第二輪:queue_b和queue_c的狀態變成completed;queue_a和queue_d狀態保持不變。
3. 第三輪:queue_d的狀態變成completed;所有佇列狀態都變成completed。
PostgreSQL還提供了一個特性——條件索引——可以最佳化查詢效能:
create index on queue_x (id) where status = 'submitted';
條件索引只給符合條件的記錄建立索引,所以索引佔用的空間就非常小。因為任務實時在排程,正常情況下處於待處理的任務並不多,所以檢索速度會非常快!
方案二:陣列倒排
方案一原理簡單,在任何關係型資料庫中都能實現。但隨著依賴數量急劇膨脹,出現超過40張表join的場景,效能急劇下降,高峰期最長耗時700多毫秒。經過研究,對方案做出了改進,用到PostgreSQL另外兩個特性——資料型別和倒排索引:
 用兩個陣列欄位取代原來任務佇列表
 submits int[]
 completes int[]
 陣列欄位建立GIN倒排索引
新方案極大簡化了表結構,不再需要一堆任務佇列表,只需要一張queues表:
create table queues (
  id bigint primary key,
  submits int[],
  completes int[]
);
不僅表結構簡化,連查詢語句也極大的簡化:
select id from queues where submits @> '{A}';
select id from queues where submits @> '{B}' and completes @> '{A}';
select id from queues where submits @> '{C}' and completes @> '{A}';
select id from queues where submits @> '{D}' and completes @> '{B,C}';
其中@>是包含運算子,意思是左邊的陣列包含了右邊陣列中所有的元素。
1. 初始狀態:submits的值是{A,B,C,D},completes的值是{}。
第一輪:A從submits移到completes,submits的值變成{B,C,D},completes的值是{A}。
2. 第二輪:B和C從submits移到completes,submits的值變成{D},completes的值是{A,B,C}。
3. 第三輪:D從submits移到completes,submits的值變成{},completes的值是{A,B,C,D}。
最佳化前,40張表join的方法,平均耗時在350ms左右,最大耗時會超過700ms;最佳化後,平均耗時在35ms以下,業務高峰期的最大耗時也只有45ms。下降了一個數量級!
 

總結
前文總共用到了PostgreSQL資料庫的9個特性:
1. 瞬間新增無預設值新列
2. JSONB型別
3. 陣列型別
4. 繼承表
5. UDF
6. 併發建立索引
7. 表示式索引
8. 條件索引
9. 倒排索引
這9個特性分別解決了風控系統中遇到的各種問題,就像是武俠中的絕世武功——獨孤九劍:遇刀破刀、遇劍破劍!PostgreSQL的特性遠不止這9個,諸如並行查詢、空間索引等都未涉及。PostgreSQL不僅僅是一個提供了SQL介面的關係型資料庫,更是一個類似Hadoop、擁有儲存能力和運算能力的開發平臺!希望本文能拋磚引玉,掌握更多PostgreSQL的特性,為開發賦能!

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31547898/viewspace-2200156/,如需轉載,請註明出處,否則將追究法律責任。

相關文章