為什麼過長欄位無效
寬表在 BI 業務中比比皆是,每次建設 BI 系統時首先要做的就是準備寬表。有時系統中的寬表可能會有上千個欄位,經常因為“過寬”超過了資料庫表字段數量限制還要再拆分。
為什麼大家樂此不疲地造寬表呢?主要原因有兩個。
一是為了
提高查詢效能
。現代 BI 通常使用關係資料庫作為後臺,而 SQL 通常使用的 HASH JOIN 演算法,在關聯表數量和關聯層級變多的時候,計算效能會急劇下降,有七八個表三四層級關聯時就能觀察到這個現象,而 BI 業務中的關聯複雜度遠遠超過這個規模,直接使用 SQL 的 JOIN 就無法達到前端立等可取的查詢需要了。為了避免關聯帶來的效能問題,就要先將關聯消除,即將多表事先關聯好採用單表儲存(也就是寬表),再查詢的時候就可以不用再關聯,從而達到提升查詢效能的目的。
二是為了
降低業務難度
。因為多表關聯尤其是複雜關聯在 BI 前端很難表達和使用。如果採用自動關聯(根據欄位型別等資訊匹配)當遇到同維欄位(如一個表有 2 個以上地區欄位)時會“暈掉”不知道該關聯哪個,表間迴圈關聯或自關聯的情況也無法處理;如果將眾多表開放給使用者來自行選擇關聯,由於業務使用者無法理解表間關係而幾乎沒有可用性;分步關聯可以描述複雜的關聯需求,但一旦前一步出錯就要推倒重來。所以,無論採用何種方式,工程實現和使用者使用都很麻煩。但是基於單表來做就會簡單很多,業務使用者使用時沒有什麼障礙,因此將多表組織成寬表就成了“自然而然”的事情。
不過,凡事都有兩面性,我們看到寬表好處而大量應用的同時,其缺點也不容忽視,有些缺點會對應用產生極大影響。下面來看一下。
寬表的缺點
資料冗餘容量大
寬表不符合正規化要求,將多個表合併成一個表會存在大量冗餘資料,冗餘程度跟原表資料量和表間關係有關,通常如果存在多層外來鍵表,其冗餘程度會呈指數級上升。大量資料冗餘不僅會帶來儲存上的壓力(多個表組合出來的寬表數量可能非常多)造成資料庫容量問題,在查詢計算時由於大量冗餘資料參與運算還會影響計算效能,導致雖然用了寬表但仍然查詢很慢。
資料錯誤
由於寬表不符合三正規化要求,資料儲存時可能出現一致性錯誤(髒寫)。比如同一個銷售員在不同記錄中可能儲存了不同的性別,同一個供應商在不同記錄中的所在地可能出現矛盾。基於這樣的資料做分析結果顯然不對,而這種錯誤非常隱蔽很難被發現。
另外,如果構建的寬表不合理還會出現彙總錯誤。比如基於一對多的 A 表和 B 表構建寬表,如果 A 中有計算指標(如金額),在寬表中就會重複,基於重複的指標再彙總就會出現錯誤。
靈活性差
寬表本質上是一種按需建模的手段,根據業務需求來構建寬表(雖然理論上可以把所有表的組合都形成寬表,但這隻存在於理論上,如果要實際操作會發現需要的儲存空間大到完全無法接受的程度),這就出現了一個矛盾:BI 系統建設的初衷主要是為了滿足業務靈活查詢的需要,即事先並不知道業務需求,有些查詢是在業務開展過程中逐漸催生出來的,有些是業務使用者臨時起意的查詢,這種靈活多變的需求採用寬表這種要事先加工的解決辦法極為矛盾,想要獲得寬表的好就得犧牲靈活性,可謂魚與熊掌不可兼得。
可用性問題
除了以上問題,寬表由於欄位過多還會引起可用性低的問題。一個事實表會對應多個維表,維表又有維表,而且表之間還可能存在自關聯 / 迴圈關聯的情況,這種結構在資料庫系統中很常見,基於這些結構的表構建寬表,尤其要表達多個層級的時候,寬表字段數量會急劇增加,經常可能達到成百上千個(有的資料庫表有欄位數量限制,這時又要橫向分表),試想一下,在使用者接入介面如果出現上千個欄位要怎麼用?這就是寬錶帶來的可用性差的問題。
總體來看,寬表的壞處在很多場景中經常要大於好處,那為什麼寬表還大量橫行呢?
因為沒辦法。一直沒有比寬表更好的方案來解決前面提到的查詢效能和業務難度的問題。其實只要解決這兩個問題,寬表就可以不用,由寬表產生的各類問題也就解決了。
SPL+DQL 消滅寬表
藉助開源集算器 SPL 可以完成這個目標。
SPL(Structured Process Language)是一個開源結構化資料計算引擎,本身提供了不依賴資料庫的強大計算能力,SPL 內建了很多高效能演算法,尤其是對關聯運算做了最佳化,對不同的關聯場景採用不同的手段,可以大幅提升關聯效能,從而不用寬表也能實時關聯以滿足多維分析時效性的需要。同時,SPL 還提供了高效能儲存,配合高效演算法可以進一步發揮性能優勢。
只有高效能還不夠,SPL 原生的計算語法不適合多維分析應用接入(生成 SPL 語句對 BI 系統改造較大)。目前大部分多維分析前端都是基於 SQL 開發的,但 SQL 體系(不用寬表時)在描述複雜關聯計算上又很困難,基於這樣的原因,SPL 設計了專門的類 SQL 查詢語法 DQL(Dimensional Query Language)用於構建語義層。前端生成 DQL 語句,DQL Server 將其轉換成 SPL 語句,再基於 SPL 計算引擎和儲存引擎完成查詢返回給前端,實現全鏈路 BI 查詢。需要注意的是,SPL 只作為計算引擎存在,前端介面仍要由使用者自行實現(或選用相應產品)。
SPL:關聯實現技術
SPL 如何不用寬表也能實現實時關聯以滿足效能要求的目標?
在 BI 業務中絕大部分的 JOIN 都是等值 JOIN,也就是關聯條件為等式的 JOIN。SPL 把等值關聯分為外來鍵關聯和主鍵關聯。
外來鍵關聯
是指用一個表的非主鍵欄位,去關聯另一個表的主鍵,前者稱為事實表,後者稱為維表,兩個表是多對一的關係,比如訂單表和客戶表。
主鍵關聯
是指用一個表的主鍵關聯另一個表的主鍵或部分主鍵,比如客戶表和 VIP 客戶表(一對一)、訂單表和訂單明細表(一對多)。
這兩類 JOIN 都涉及到主鍵,如果充分利用這個特徵採用不同的演算法,就可以實現高效能的實時關聯了。
不過很遺憾,SQL 對 JOIN 的定義並不涉及主鍵,只是兩個表做笛卡爾積後再按某種條件過濾。這個定義很簡單也很寬泛,幾乎可以描述一切。但是,如果嚴格按這個定義去實現 JOIN,理論上沒辦法在計算時利用主鍵的特徵來提高效能,只能是工程上做些有限的最佳化,在情況較複雜時(表多且層次多)經常無效。
SPL 改變了 JOIN 的定義,針對這兩類 JOIN 分別處理,就可以利用主鍵的特徵來減少運算量,從而提高計算效能。
外來鍵關聯
和 SQL 不同,SPL 中明確地區分了維表和事實表。BI 系統中的維表都通常不大,可以事先讀入記憶體建立索引,這樣在關聯時可以少計算一半的 HASH 值。
對於多層維表(維表還有維表的情況)還可以用
外來鍵地址化
的技術做好
預關聯
。即將維表(本表)的外來鍵欄位值轉換成對應維表(外來鍵表)記錄的地址。這樣被關聯的維表資料可以直接用地址取出而不必再進行 HASH 值計算和比對,多層維表僅僅是多個按地址取值的時間,和單層維表時的關聯效能基本相當。
類似的,如果事實表也不大可以全部讀入記憶體時,也可以透過預關聯的方式解決事實表與維表的關聯問題,提升關聯效率。
預關聯可以在系統啟動時一次性讀入並做好,以後直接使用即可。
當事實表較大無法全記憶體時,SPL 提供了
外來鍵序號化
方法:將事實表中的外來鍵欄位值轉換為維表對應記錄的序號。關聯計算時,用序號取出對應維表記錄,這樣可以獲得和外來鍵地址化類似的效果,同樣能避免 HASH 值的計算和比對,大幅提升關聯效能。
主鍵關聯
有的事實表還有明細表,比如訂單和訂單明細,二者透過主鍵和部分主鍵進行關聯,前者作為主表後者作為子表(還有透過全部主鍵關聯的稱為同維表,可以看做主子表的特例)。主子表都是事實表,涉及的資料量都比較大。
SPL 為此採用了
有序歸併
方法:預先將外存表按照主鍵有序儲存,關聯時順序取出資料做歸併,不需要產生臨時快取,只用很小的記憶體就可以完成計算。而 SQL 採用的 HASH 分堆演算法複雜度較高,不僅要計算 HASH 值進行對比,還會產生臨時快取的讀寫動作,運算效能很差。
HASH 分堆技術實現並行困難,多執行緒要同時向某個分堆快取資料,造成共享資源衝突;某個分堆關聯時又會消費大量記憶體,無法實施較大的並行數量。而有序歸則易於分段並行。資料有序時,子表就可以根據主表鍵值進行同步對齊分段以保證正確性,無需快取,且因為佔用記憶體很少可以採用較大的並行數,從而獲得更高效能。
預先排序的成本雖高,但是一次性做好即可,以後就總能使用歸併演算法實現 JOIN,效能可以提高很多。同時,SPL 也提供了在有追加資料時仍然保持資料整體有序的方案。
對於主子表關聯 SPL 還可以採用更有效的儲存形式將主子表一體化儲存,子表作為主表的集合欄位,其取值是由與該主表資料相關的多條子表記錄構成。這相當於預先實現了關聯,再計算時直接取數計算即可,不需要比對,儲存量也更少,效能更高。
儲存機制
高效能離不開有效的儲存。SPL 也提供了
列式儲存
,在 BI 計算中可以大幅降低資料讀取量以提升讀取效率。SPL 列存採用了獨有的
倍增分段
技術,相對傳統列存分塊並行方案要在很大資料量時(否則並行會受到限制)才會發揮優勢不同,這個技術可以使 SPL 列存在資料量不很大時也能獲得良好的並行分段效果,充分發揮並行優勢。
SPL 還提供了針對資料型別的最佳化機制,可以顯著提升多維分析中的切片運算效能。比如將列舉型維度轉換成整數,在查詢時將切片條件轉換成布林值構成的對位序列,在比較時就可以直接從序列指定位置取出切片判斷結果。還有將多個標籤維度(取值是或否的維度,這種維度在多維分析中大量存在)儲存在一個整數字段中的
標籤位維度
技術(一個整數字段可以儲存 16 個標籤),不僅大幅減少儲存量,在計算時還可以針對多個標籤同時做按位計算從而大幅提升計算效能。
有了這些高效機制以後,我們就可以在 BI 分析中不再使用寬表,轉而基於 SPL 儲存和演算法做實時關聯,效能比寬表還更高(沒有冗餘資料讀取量更小,更快)。
不過,只有這些還不夠,SPL 原生語法還不適合 BI 前端直接訪問,這就需要適合的語義轉換技術,透過適合的方式將使用者操作轉換成 SPL 語法進行查詢。
這就需要 DQL 了。
DQL:關聯描述技術
DQL 是 SPL 之上的語義層構建工具,在這一層完成對於 SPL 資料關聯關係的描述(建模)再為上層應用服務。即將 SPL 儲存對映成 DQL 表,再基於表來描述資料關聯關係。
透過對資料表關係描述以後形成了一種以維度為中心的匯流排式結構(不同於 E-R 圖中的網狀結構),中間是維度,表與表之間不直接相關都透過維度過渡。
基於這種結構下的關聯查詢(DQL 語句)會很好表達。比如要根據訂單表(orders)、客戶表(customer)、銷售員表(employee)以及城市表(city)查詢:
本年度華東的銷售人員,在全國各銷售區的銷售額
。
用 SQL 寫起來是這樣的:
SELECT ct1。area,o。emp_id,sum(o。amount) somtFROM orders o JOIN customer c ON o。cus_id = c。cus_id JOIN city ct1 ON c。city_id = ct1。city_id JOIN employee e ON o。emp_id = e。emp_id JOIN city ct2 ON e。city_id = ct2。city_idWHERE ct2。area = ‘east’ AND year(o。order_date)= 2022GROUP BY ct1。area, o。emp_id
多個表關聯要 JOIN 多次,同一個地區表要反覆關聯兩次才能查到銷售員和客戶的所在區域,對於這種情況 BI 前端表達起來會很吃力,如果將關聯開放出來,使用者又很難理解。
那麼 DQL 是怎麼處理的呢?
DQL 寫法:
SELECT cus_id。city_id。area,emp_id,sum(amount) somtFROM ordersWHERE emp_id。city_id。area == “east”AND year(order_date)== 2022BY cus_id。city_id。area,emp_id
DQL 不需要 JOIN 多個表,只基於 orders 單表查詢就可以了,外來鍵指向表的欄位當成屬性直接使用,有多少層都可以引用下去,很好表達。像查詢客戶所在地區透過 cus_id。city_id。area 一直寫下去就可以了,這樣就消除了關聯,將多表關聯查詢轉化成單表查詢。
更進一步,我們再基於 DQL 開發 BI 前端介面就很容易,比如可以做成這樣:
用樹結構分多級表達多層維表關聯,這樣的多維分析頁面不僅容易開發,普通業務使用者使用時也很容易理解,這就是 DQL 的效力。
總結一下
,寬表的目的是為了解決 BI 查詢效能和前端工程實現問題,而寬表會帶來資料冗餘和靈活性差等問題。透過 SPL 的實時關聯技術與高效儲存可以解決效能問題,而且效能比寬表更高,同時不存在資料冗餘,儲存空間也更小(壓縮);DQL 構建的語義層解決了多維分析前端工程的實現問題,讓實時關聯成為可能,,靈活性更高(不再侷限於寬表的按需建模),介面也更容易實現,應用範圍更廣。
SPL+DQL 繼承(超越)寬表的優點同時改善其缺點,這才是 BI 該有的樣子。
SPL下載地址:http://c。raqsoft。com。cn/article/1595816810031
SPL開源地址:https://github。com/SPLWare/esProc