什麼是大報表?如何解決大報表的問題?

bubblegum發表於2020-07-21

實際業務中有些報表比較“大”,查詢出的報表資料行數可以達到幾千萬甚至上億,這類行數很多的報表通常被成為“大報表”。大報表大部分情況下是清單明細報表,少量是分組報表。

大報表查詢通常不會採用一次性取出所有記錄再交給前端呈現的方式,因為這樣要等很久,使用者體驗極差;而且報表伺服器記憶體也吃不消。

常見的方式是透過分頁來呈現大報表,一次只取一小部分資料,取數結束後立刻交給前端呈現,當頁碼變化時再取出相應頁數的資料,這樣可以加快報表呈現速度,使用者幾乎沒有等待感。

具體如何實現呢?有幾種方式。

1. 資料庫分頁
業界最常用的做法是使用資料庫分頁來實現,具體來講,就是利用資料庫提供的返回指定行號範圍內記錄的語法。介面端根據當前頁號計算出行號範圍(每頁顯示固定行數)作為引數拼入 SQL 中,資料庫就會只返回當前頁的記錄,從而實現分頁呈現的效果。

主要藉助關聯式資料庫自身的能力,每種資料庫實現上會有所差異,Oracle 可以使用 rownum,mysql 則可以 limit,具體實現網上有很多資料這裡不再贅述。

資料庫分頁有沒有什麼不足?任何技術都有其應用範圍,資料分頁的問題主要集中在以下 4 點。

(1)翻頁時效率較差
用這種辦法呈現第一頁一般都會比較快,但向後翻頁時,所使用的取數 SQL 會被再次執行,並且將前面頁涉及的記錄跳過。對於有些沒有 OFFSET 關鍵字的資料庫,就只能由介面端自行跳過這些資料(取出後丟棄),而像 ORACLE 還需要用子查詢產生一個序號才能再用序號做過濾。這些動作都會降低效率,浪費時間,前幾頁還感覺不明顯,但如果頁號比較大時,翻頁就會有等待感了。

(2) 可能出現資料不一致
用這種辦法翻頁,每次按頁取數時都需要獨立地發出 SQL。這樣,如果在兩頁取數之間又有了插入、刪除動作,那麼取的數反映的是最新的資料情況,很可能和原來的頁號匹配不上。例如,每頁 20 行,在第 1 頁取出後,使用者還沒有翻第 2 頁前,第 1 頁包含的 20 行記錄中被刪除了 1 行,那麼使用者翻頁時取出的第 2 頁的第 1 行實際上是刪除操作前的第 22 行記錄,而原來的第 21 行實際上落到第 1 頁去了,如果要看,還要翻回第 1 頁才能看到。如果還要基於取出的資料做彙總統計,那就會出現錯誤、不一致的結果。

為了克服這兩個問題,有時候我們還會用另一種方法,用 SQL 遊標從資料庫中取數,在取出一頁呈現後,但並不終止這個遊標,在翻下一頁的時候再繼續取數。這種方法能有效地克服上述兩個問題,翻頁效率較高,而且不會發生不一致的情況。不過,絕大多數的資料庫遊標只能單向從前往後取數,表現在介面上就只能向後翻頁了,這一點很難向業務使用者交代,所以很少用這種辦法。

當然,我們也可以結合這兩種辦法,向後翻頁時用遊標,一旦需要向前翻頁,就重新執行取數 SQL。這樣會比每次分頁都重新取數的體驗好一些,但並沒有在根本上解決問題。

(3) 無法實現分組報表
除了清單報表,有時我們還要呈現大資料量的分組報表,報表包含分組、分組彙總及分組明細資料。我們知道,按頁取數按照翻頁每次讀取固定條數(一頁或幾頁)記錄,這樣根本無法保證一次性讀取一個完整分組,而分組呈現、分組彙總都要求基於整組資料來操作,否則就會出錯。

(4) 無法使用其他資料來源
資料庫分頁是藉助關聯式資料庫自身的能力,而對於非關聯式資料庫就不靈了。試想一下,NoSQL 怎麼用 SQL 分頁,文字怎麼做分頁?

報表的多樣性資料來源話題我們曾經討論過,在資料規模迅速膨脹的今天,基於非關聯式資料庫出報表已經非常普遍。


除了資料庫分頁,還有其他更好的方式嗎?

2. 硬編碼實現
我們發現基於資料庫的分頁方式強依賴資料來源,無法滿足其他資料來源型別的需要,也就是與資料庫緊耦合的。我們需要一個低耦合資料來源的實現方式以應對多樣性資料來源場景,還能同時解決效率、準確性和分組呈現問題。

按照主流的解題思路,可以透過編碼方式實現這個目標,實現思路大概是這樣:

基於資料庫時,
把取數和呈現做現兩個非同步執行緒,取數執行緒發出 SQL 後就不斷取出資料後快取到本地儲存中,呈現執行緒根據頁數計算出行數到本地快取中去獲取資料顯示。這樣,只要已經取過的資料就能快速呈現,不會有等待感,還沒取到的資料需要等待一下也是正常可理解的;而取數執行緒只涉及一句 SQL,在資料庫中是同一個事務,也不會有不一致的問題。這樣,兩個問題都能得到解決。不過這需要設計一種可以按行號隨機訪問記錄的儲存格式,不然要靠遍歷把記錄數出來,那反應仍然會很遲鈍。

基於非資料庫時,
同樣設定取數和呈現兩個非同步執行緒,取數時透過檔案遊標(或其他資料來源提供的分批取數介面)分批讀取資料並快取到本地,呈現階段則與上述方式完全一致。

然後再利用報表工具開放的介面和前端報表進行互動,完成報表的分頁呈現。

做分組報表時,
就需要一次性取出完整分組,在程式碼裡進行分組彙總,並將彙總結果插入結果集一併返回給前端報表進行呈現,同時還要設定分組記錄標誌位,以便前端報表在呈現時能夠為分組行設定不同的顯示效果(如加粗、標紅)。

實現後的效果類似下面這樣:
gif

取數執行緒不停地取數快取,呈現執行緒從快取中讀取資料呈現,總頁數不斷變化

透過上面的描述,可以看到自己硬編碼雖然可以實現,但複雜度很高。除了多執行緒程式設計,還要考慮快取資料儲存形式、檔案遊標、分組讀取與彙總,此外借助其他報表工具進行呈現時還要對方開放足夠靈活的介面,…,這些都是挑戰。

而且我們只考慮了呈現,如果還要匯出 Excel 怎麼辦?如果還要列印怎麼辦?畢竟報表既然能查就應該能匯出,能列印。這些需求在金融、製造行業都是真實存在的。


(3) 使用支援大報表的報表工具
如果前端使用報表工具開發報表,選用一個直接支援大報表呈現、匯出、列印的報表工具則更為直接。工具實現了兩個非同步執行緒的大報表機制,同時解決上面我們提到的問題,這些方面都封裝好直接使用。

大資料時代要支援大報表,這應該是報表工具必備能力。

擴充套件閱讀:



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

相關文章