SQL跑得太慢怎麼辦?

大雄45發表於2022-09-19
導讀 SQL 作為目前最常用的資料處理語言,廣泛應用於查詢、跑批等場景。當資料量較大時,使用 SQL(以及儲存過程)經常會發生跑得很慢的情況,這就要去最佳化 SQL。如果一個計算的實現過於複雜,其開發代價已經遠遠超過效能最佳化本身,那也就沒有最佳化的意義了。

SQL 作為目前最常用的資料處理語言,廣泛應用於查詢、跑批等場景。當資料量較大時,使用 SQL(以及儲存過程)經常會發生跑得很慢的情況,這就要去最佳化 SQL。最佳化 SQL 有一些特定的套路,通常先要檢視執行計劃來定位 SQL 慢的原因,然後針對性改寫來最佳化 SQL,比如對於連續數值判斷可以用 between 來替代 in,select 語句指明欄位名稱,用 union all 替代 union,把 exists 改寫成 join 等。當然還有一些工程上的最佳化手段,如建立索引,使用臨時表 / 彙總表等,最佳化的方法有很多,相信各位 DBA 都不會陌生。

但遺憾的是,仍然有相當多情況無論怎樣最佳化都不可能跑得更快。這裡 做 SQL 效能最佳化真是讓人乾瞪眼 介紹了一些,並做了相應的技術分析。由於其理論基礎關係代數的侷限,SQL 缺乏離散性和有序集合等特性的支援使得 SQL 在表達某些高效能演算法時異常困難,甚至完全寫不出來,只能採用比較笨的低效能演算法,眼睜睜地看著硬體資源被白白浪費。在 寫著簡單跑得又快的資料庫語言 SPL 中有對 SQL 理論基礎缺陷的通俗解釋。

也就是說,SQL 的慢是理論性的,這種問題僅僅由資料庫在工程層面最佳化只能區域性改善(確實有不少商業資料庫能夠自動識別某些 SQL 並轉換成高效能演算法),而不能根本地解決問題(情況複雜時資料庫最佳化引擎都會“暈”掉,只能按 SQL 的書寫邏輯執行成低效能演算法)。

理論性的缺陷當然也不能寄希望於更換資料庫而得到解決,只要還是用 SQL,即使採用分散式資料庫、記憶體資料庫也還是這種情況,在消耗更大成本的資源後當然也能有一定的效能提升,但和硬體本應能夠達到的效能仍然有巨大的差距。

那還能怎麼辦?

那就不能再用 SQL!也就不能再用關聯式資料庫了。

那用什麼?

SQL 描述不了這些高效能演算法,用 Java,C++ 行嗎?

沒問題!從理論上講,Java、C++ 什麼演算法都能實現,而且因為可以控制計算機底層的動作,這類程式碼通常可以跑出很好的速度(只要程式設計師能力不是太差)。

不過,不要高興得太早,雖然都寫得出來,但由於這些開發語言過於原生,本身沒有提供什麼面向資料處理的高效能運算類庫,想實現這些演算法就必須從頭實現,而這恐怕要累死。以雜湊關聯為例,Java 實現至少要寫幾百行程式碼,不僅要設計合適的雜湊函式,還要解決可能出現的雜湊衝突,這一套下來的工程量可不小;還有在 Java 中進行多執行緒程式設計也並非易事,但平行計算又是提升計算效能的有效手段。類似的,涉及結構化資料計算的演算法還有很多,這些都自己來做的複雜度可想而知。如果一個計算的實現過於複雜,其開發代價已經遠遠超過效能最佳化本身,那也就沒有最佳化的意義了。

Python 也面臨類似的問題,雖然在結構化資料計算類庫方面要比 Java 豐富得多,但並沒有提供必要的高效能演算法庫和儲存方案,比如沒有提供為大資料服務的遊標型別及相關運算,也沒提供有效的並行機制。想要實施那些高效能演算法也只能自己開發,但 Python 作為解釋執行語言,本身執行效率不高,在此基礎上再開發的演算法也往往達不到高效能要求。同樣,Scala 也缺乏足夠的高效能運算類庫,自己編寫的演算法同樣複雜度相當高,對於不熟悉這些演算法的程式設計師來講,從頭實現的程式碼的執行效率往往還不如努力最佳化後商用資料庫 SQL 的速度。

那就只能忍受 SQL 的慢了嗎?

還可以用 SPL!

SPL 和高效能

開源 SPL(Structured Process Language),一個專門面向結構化資料處理的程式語言。使用 SPL 可以讓原本 SQL 跑得慢的計算變快。

為什麼 SPL 能跑得快?是擁有了什麼改變硬體效能的黑科技嗎?

那倒沒有。軟體改變不了硬體的計算效能,SPL 也一樣。簡單來說,SPL 快就是上面說的,要使用更高效能的演算法。SPL 中提供了大量基礎的高效能演算法類庫,基於這些演算法庫實現的程式碼可以有效減少計算量,而我們做計算就是組合運用這些演算法,每種計算都快一些,那整體上就會快很多,從而達到提升計算效能的目的。

SPL 設計的這些高效能演算法,像遍歷複用、有序歸併、外來鍵預關聯、標籤位維度、平行計算等,都已經封裝好。這其中有很多演算法都是 SPL 獨創的,在業內也是首次出現。

SQL跑得太慢怎麼辦?SQL跑得太慢怎麼辦?

基於這些封裝好的演算法庫,再寫程式會就很方便,拿來直接用而不需要從頭自己開發,不僅效能高,開發也快。從這個角度來看,跑得快和寫著簡單其實是一回事,就是能高效率地編寫高效能演算法。反觀 Java、C++、Python、Scala 由於缺少這些演算法庫,想要實現高效能也就很難了。

SPL 採用和 SQL 不同的觀念看待同一個計算任務,繼而可以採用不同(更低)複雜度的計算方法。

在實戰中,SPL 目前已經做過不少效能最佳化案例,少則提速數倍,多則數十倍,極端情況還有提速上千倍的,提速一個數量級基本上是常態。

比如在最佳化某保險公司車險保單跑批的案例( 開源 SPL 最佳化保險公司跑批優從 2 小時到 17 分鐘)中,使用 SPL 將計算時間從 2 小時縮短到 17 分鐘,同時程式碼量減少了 2/3。這裡使用了 SPL 特有的遍歷複用技術,可以在對大資料的一次遍歷過程中實現多種運算,有效地減少外存訪問量。這個案例涉及對一個大表進行三次關聯和彙總的運算,使用 SQL 要將大表遍歷三次,而使用 SPL 只需要遍歷一次,並在關聯運算上也採用了不同的方法,因此獲得了巨大的效能提升。

還有在 開源 SPL 將銀行手機賬戶查詢的預先關聯變成實時關聯 的案例中,使用 SPL 將原本只能預關聯的手機賬戶查詢變成實時關聯,同時伺服器數量從 6 臺降為 1 臺。這裡充分利用了 SPL 的有序儲存機制,一次性讀取整個賬戶資料時可以有效減少硬碟時間(物理儲存連續),再借助區分維表和事實表的外來鍵實時關聯技術使用單機就能完成實時關聯查詢,效能提升明顯,硬體需求也降低了許多。

進一步討論

說到這裡你可能會想,那是不是學會 SPL 語法就能把計算跑得快了?

也沒這麼簡單!

關於演算法

使用 SPL 可以獲得更高效能不是因為 SPL 語法,SPL 語法雖然有些特色,但並不是跑得快的根本原因。最關鍵的是掌握和運用高效能演算法。

實現效能最佳化有兩步:第一步設計出低複雜的計算方案,第二步用足夠低的成本實現它。其中更關鍵的是第一步,這需要由有一定經驗和知識儲備的程式設計師來做(即掌握和運用高效能演算法),第二步才是用 SPL 來做。換句話說,SPL 並不負責設計解決問題的方法,而只是負責讓解法更容易實現出來。

SPL 語法很簡單,比 Java 容易得多,兩小時就能基本上手,兩三週就能比較熟練了。但演算法卻沒那麼簡單,需要認真學習反覆練習才能掌握。反過來,只要掌握了演算法,用什麼語法就是個相對次要的問題了(當然用 SQL 這種太粗線條的語言還是不行)。這就像給病人看病,找出病理原因後,能分析出什麼成分的藥能管用。無論直接購買成藥(使用封裝過的 SPL),還是上山採藥(使用 Java/C++ 硬寫),都可以治好病,無非就是麻煩程度和支付成本不同。

因為實際中很少使用,有不少應用程式設計師工作幾年後都把大學時代學過的資料結構和演算法課程內容忘了,而不理解這些基礎演算法知識時也就沒辦法設計出高效能演算法方案。為此,SPL 設定了專門的高效能專題,不僅涵蓋高效能演算法與最佳化技巧,還有效能最佳化課程與效能圖書來授人以漁。

高效能運算專題

效能最佳化圖書

效能最佳化課程

關於儲存

和演算法密切相關的,高效能運算還有一個關鍵點是資料儲存,高效能運算離不開合理的資料儲存方式。使用 SPL 實施高效能運算時也不能再基於資料庫來做,需要將資料從資料庫裡搬出來重新組織。

為什麼呢?

慢的資料計算任務可以分為計算密集型和資料密集型兩大類。單純的計算密集型任務涉及的資料量不大而只是計算量很大,計算量大並不是由於資料量大造成的,這樣不用改變儲存方式,只要實施了好的計算方法也能大幅提升效能,也就是說,可以繼續在原有的儲存方式(比如資料庫)上使用 SPL 來最佳化效能。而資料密集型任務涉及的計算量也很大,但計算量大主要是由資料量大造成的,這時候如果不改變儲存方式,資料讀取時間很可能會很長,即使能把計算時間最佳化到 0,整體運算時間也不能得到有效的最佳化。

遺憾的是,我們面臨的計算慢的場景絕大部分屬於資料密集型計算。如果資料還存在資料庫中,而資料庫取數介面(如 JDBC)通常又非常慢,將資料讀出就要消耗很長時間(IO 效率很低),經常遠遠超過後續 SPL 計算的時間,這也就不可能達到最佳化效果了。而且,SPL 中有相當多的演算法也對儲存組織有要求,比如單邊分堆演算法就要求有序的儲存方式,而常規關聯式資料庫無法滿足這個前提,這些演算法也無法實施了。

為了解決這個問題,SPL 提供了自有的儲存機制,直接採用檔案系統,將資料從資料庫匯出到特定格式的檔案中,不僅可以獲得更高的 IO 存取效率以及檔案系統靈活的管理能力,還可以充分利用自有格式的列存、有序、壓縮、並行分段等資料儲存優勢,從而高效地發揮高效能演算法效力。

使用檔案儲存資料還可以有效減少資料入庫的時間,進一步提升計算效能。有些計算場景不僅要從資料來源讀,還要將計算結果落地,存入資料庫以方便後續計算使用。像 ETL 就是典型的讀寫並存的計算,還有某些大資料計算或複雜計算需要將中間結果暫存,後續計算還需要再使用的情況。我們知道,資料庫寫入是非常慢的動作,伴隨寫入的計算場景效能自然低下。這時就可以將原本需要入庫的資料儲存在檔案中(雖然這是工程方面的優勢,但仍可獲得接近數量級的讀寫效能提升),再利用 SPL 的檔案計算能力直接計算,從而實現高效能。

關於 T+0

如果把資料都移到資料庫外,那麼是不是就無法完成實時資料計算了?畢竟資料總是在不斷地產生。

沒有問題。

對於全量 T+0 實時查詢,SPL 提供了多源混合計算的能力以滿足這類場景。冷資料量大且不再變化使用 SPL 的高效能檔案儲存,這樣可以獲得更高地計算效能;熱資料量小仍然存放在原有資料來源中,SPL 直接讀取計算(支援多樣性資料來源),由於熱資料量並不大,直接基於生產資料來源查詢也不會對其造成太大影響,訪問時間也不會太長。冷熱資料混合計算,就可以獲得針對全量資料的 T+0 實時查詢。我們只要定期將變冷的資料固化到 SPL 的高效能儲存中,原資料來源只需要保持少量近期新產生的熱資料即可。整體架構如下:

SQL跑得太慢怎麼辦?SQL跑得太慢怎麼辦?

如何開始

從前面的分析可以知道,完成效能最佳化任務必須熟悉高效能演算法和儲存機制,但從上面的課程圖書也可以看出來,這些內容並不少,都要融會貫通也不是很容易的事。特別是很多程式設計師都習慣了 SQL 的思維方式,很難跳出這個窠臼。面對一個效能最佳化任務,即使有了開源 SPL 這樣的有利武器,也常常有點無從下手。打個比方,一個趕馬車的高手想跑得更快時,會習慣於尋找韁繩和鞭子,而對於初次見到的汽車上的方向盤和油門卻會感到一頭霧水。

為此,SPL 團隊也提供相應的諮詢服務:你可以把遇到的效能問題拿過來與我們一起討論和設計最佳化方案,必要的時候還可以進行 POC。

我們通常關心這樣一些必要的問題資訊:業務場景、面臨痛點、當前計算的資料量和併發量以及響應時間,如果還能提供 SQL  、表結構和測試資料就更好了。

相信我們,從不失手!

經歷過一兩個案子,程式設計師們就會熟悉 SPL 的思維方式(理解了方向盤和油門),以後再自己做效能最佳化就不是問題了。

天下武功,唯快不破,但只有掌握了快的本質和方法才能所向無敵。你說是不是?

原文來自:


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

相關文章