限定兩小時!一次由權限類型歸集引發的緊急SQL優化案例
編者按
《SQL性能優化與批判》是黃浩老師的系列新作,他將從過往在項目技術支持中碰到的諸多案例入手,細化到每一條問題SQL的內在病因,反思每一個案例的背后深思。今天跟大家分享的是第四個案例:獲取責任人,需要回顧前情的同學請戳這里:案例一、案例二、案例三。
一、案例
這一天,I項目組的一個迭代版本需要上線,這是一個大版本,需要全員現場支撐,并要求上線后三天待命。
1、不速之客,來者不善
而就在上線前兩天,即9月24日下午4點鐘,一直以來波瀾不驚有驚無險的性能優化,突然被放了一個大招,某個頁面被測出了嚴重的性能問題,大致情況如下:
測試人員在性能環境做了一輪壓力測試,數據增加了5倍,其它功能點基本上達到了性能指標,而該功能則需要6s,整整超出了3s。
瞬間,大家都緊張起來了。
由于0926版本是公司級的大版本,不光是I項目組發布版本,H公司的其它系統也會同時發布版本。為了控制風險,會提前兩天凍結代碼。按照“不帶BUG上生產”的原則,我們必須要在版本凍結截至時(9月24日18點準)“斃”掉這個性能BUG單。而距離18點還不到2個小時。
PM在得知這一消息后也高度關注,責令優化小組全力攻關,要人給人。這樣,組長、模塊SE及我就組成了臨時應急小組。大家全力以赴,很快就把問題梳理出來了,大致如下:
該頁面加載共需要執行8條SQL,單條SQL的執行都不長,都在性能指標范圍內,但是加起來超過了5s;
剩下的2s耗在頁面的邏輯處理。
當時,組長當機立斷,一方面要求對這些SQL進行優化,優化到2s左右;另一方面將頁面的處理耗時降到1s內,這樣就能確保3s的性能要求。
SQL優化任務自然落在我的頭上,8個SQL的代碼如下:
2、兵分兩路,把雞蛋放在兩個籃子里
看著這8個不長不短整整齊齊的SQL,我的***反應是:一個頁面加載怎么會存在8個SQL語句?這8個SQL之間又有著什么樣的關聯關系?是否還可以合并成一個?
如果做SQL合并的話,就意味著我需要詳讀這8個SQL,但時間的指針已經指在了17:00,離18:00下班不足一個小時。用中國足球賽事評論員的話說就是“現在留給中國對的時間已經不多了”,已經沒有時間讓我解讀這8個SQL;況且,即便能快速解讀,也未必能合并。
那么就要像組長提議的:尋求單個SQL的優化突破。而8個SQL優化到2秒,也就是說單個SQL平均耗時在0.25秒,這個壓力也是非常大的。
我在與組長簡短商議后,為了降低風險,不至于孤注一擲,做出了如下決定:兵分兩路,由我執行合并方案,優化小組的DBA負責單個SQL進行優化。
3、原來如此,不過如此
按照以往的習慣,我肯定會先自己解讀這8個SQL,因為我相信別人的時間也是時間,能自己解決的盡量不要占用別人的時間。但這次不行了,因為時間不允許了,我必須要快速了解8個SQL的業務功能。
于是我跟SE表達了我訴求,SE立即安排了開發責任人跟我對接。在與開發人員長達20分鐘的溝通后,終于理清了這個8個SQL的邏輯與關系,如下:
查詢任務列表,共3個SQL,共耗時1s,主SQL,包括了count和詳情
查詢責任人:4個SQL,共耗時3s,但是頁面自上而下共耗時5s
查詢網絡節點:1個SQL共耗時0.5s
這是個重大發現:6s多的時間中,查詢責任人花費了5s,這是要重點照顧的對象。我繼續向開發責任人了解更多的信息:
“查詢責任人SQL,SQL單獨運行是3s,為何頁面卻花費了5s?”
“因為頁面需要對SQL返回的數據集進行判斷。”
“都做了哪些邏輯處理?”
“這四個SQL分別對應四類權限,權限的最小單元是實體DU,在任務列表中獲取的DU,先用***個SQL判斷哪些DU具有***類權限,比如有100個,那么傳入第二個SQL的DU就是90個DU,由此類推,知道完成了4類權限的判。”
聽完后,我豁然開朗,邏輯流程圖如下:
4、對癥下藥,一蹴而就
至此,我已成竹在胸。
四個SQL對應四種權限,如果我們把TASK_ID比作學生,把USER_ID比作班級,而將權限比作是學生選修的四門學科。那么“權限責任人查詢”就轉變成查詢當前班級每個學生***分的科目。
這是典型的按優先級排序后取***值的需求。當前的方案是:
- 依次從DB中獲取四種權限對應的DU_ID;
- 在JAVA中根據DB返回的權限判斷權限類型。
該方案存在兩個性能瓶頸:
- 將權限數據從DB傳輸到JAVA服務器是要一定的成本開銷的;
- 當JAVA拿到權限數據數據時,需要循環逐一歸集權限類型,這個過程也會帶來一定的性能問題。
如果我們能將權限類別歸集放在DB中完成,即DB只需要返回當前用戶的DUID所屬權限類別即可,那么至少省卻了4次數據傳輸的時耗。當然,權限類型歸集無論是放在DB還是JAVA,都是需要成本開銷,就看誰的算法更具優勢。事實上Oracle則提供了完整的解決方案,即用rank over來實現優先級排序。
此時時間已經到了17:20,我來不及多想,立馬對查詢責任人的4個SQL進行合并改寫,合并后的SQL如下:
改寫后,放在DB中執行,耗時0.98秒。這意味著,責任人查詢從5s成功降到了1s內,足足下降了4s;這樣,整體上也完全滿足性能要求。
我在17:25將SQL移交給了開發,留給開發人員35分鐘時間去開發驗證。
結果自然是皆大歡喜,項目順利上線。
二、心得
1、學無止境的態度
當SE拿到我合并的SQL后,滿臉的疑惑:
“這個SQL會不會有問題?”
“我是按照業務需求改寫的,如果我沒有錯誤理解需求的話,SQL就是正確的。”
“也是,我測試了好幾種場景,結果看起來都是正確的。”
接著我又詳細講解了Rank的功用和用法。SE長吁一聲說道:“早知道Oracle有如此“神器”,當初就也不用費老大勁在Java中做權限類型歸集了,還弄出了性能問題。看來真的是學無止境呀。”
在此,我無意于苛求SE“早知如此,何必當初”,畢竟術業有專攻。唯一不解的是,偌大的一個項目組(近200人),居然沒有配置一名DB開發工程師。建表,寫SQL這些活都是由Java開發人員包辦。而在與Java開發工程師溝通中了解到,部分人員根本沒有SQL基礎,更不用說是開發經驗。而他們寫SQL的方式即簡單又粗暴,是從同事那里拿一個功能類似的SQL,直接在此基礎上修改,也不知道該SQL的具體含義。
這種現學現賣的方式也直接導致了很大的性能問題。正是因為確實了DB開發工程師的崗位配置,大大弱化了SQL功能,使得DB退化成為僅僅是數據存儲功能,失去了真正的核心:組織和管理功能。
作為不僅僅是世界500強的企業,作為國內代表***開發水準的企業,在企業管理系統的開發項目中,尚且不配置專職DB開發工程師,而其它企業的開發團隊的人員配置就更可想而知了。
2、點到為止的哲學
在組長的運籌帷幄下,性能優化小組在緊張備戰1017版本性能攻關的同時,很好地保障了926版本的性能需求,使得926版本順利上線,I項目PM也揚眉吐氣了一把:在性能紅線上,終于沒有求爺爺告奶奶放一馬了。在926版本上線后,一方面為表謝意,另一方面也為1017版本打氣,PM宴請了小組成員,席間問起:
“黃工,就你來看,項目在SQL這方面還有多大的優化空間?”
“這要看領到對性能的要求和優化的決心了。”
“怎么說?”
“真正的優化,***的空間還是在于從底層的模型設計,以及寫出規范和優秀的SQL,因此應該在項目上配置專職的DBA………..”
“呃,黃工,這樣可不行,如果真的是這樣了,那你們干嘛呢?”
領導就是領導,不正面沖突,在輕描淡寫中已經說明了一切,而后來我在內部資料中看到“現固化,再僵化,后優化”的流程策略時,就更明白了。