1. 程式人生 > 實用技巧 >2. 數倉理論

2. 數倉理論

正規化理論

正規化可以理解為設計一張符合標準級別的資料表結構時,所需要遵循的規範和要求。

而在關係型資料庫設計時遵照一定的規範要求,可以帶來很多好處。比如:降低資料的冗餘性:

  • 1. 因為資料冗餘度高的話, 會增大磁碟開銷
  • 2. 在不使用分散式系統的情況下, 資料冗餘度高的話, 可能需要增加磁碟的數量, 而單機的磁碟個數是有限的
  • 3. 資料冗餘度高的話, 會導致一次修改需要修改多個表, 很難保證資料的一致性

舉個栗子:

假設有兩張表,一張表儲存使用者的資訊和購買過的商品、數量、以及金額;另一張表存放的是使用者資訊和它評價了哪些商品、蒐藏了哪些商品。但是我們看到這樣做會造成資料冗餘,因為使用者資訊被儲存了兩次,所以這個時候可以將使用者的資訊單獨抽出來作為一張使用者表,並設定一個使用者id。然後別的表只需要儲存使用者id即可,這樣可以避免不必要的資料儲存。

而且當用戶修改資訊之後,只需要更新使用者表的資訊即可,不會影響其它的表。但如果不把使用者資訊單獨作為一張表的話,那麼當用戶修改資訊時每張表都需要改一遍。

所以在如果設計上不遵循規範的話,那麼就會造成許多問題,再舉個栗子:

這種表設計相當於將員工資訊、所在部門以及職位資訊都儲存到一個表中了,顯然會存在如下問題:

  • 資料冗餘: 同一個部門的資訊儲存了多份, 需要佔用更多的磁碟空間; 資料冗餘有時候也可能是在不同的表中儲存了重複的資料, 比如最開始的那個關於購買商品的栗子;
  • 插入異常: 如果想要成立一個新的部門, 由於還沒有增加新的員工, 因此無法錄入這個部門的資訊;
  • 刪除異常: 如果某個部門的所有員工都被刪除, 該部門的資訊也將不復存在;
  • 更新異常: 如果需要修改部門資訊, 那麼會需要更新多行資料, 效率低下; 不小心忽略了某些記錄的話,將會導致資料不一致;

為了解決這些問題,資料庫引入了規範化過程。而規範化便使用我們上面說的"正規化"來定義和衡量。關係模式的創始人 Edgar Codd 最早提出了第一正規化(1NF)、第二正規化(2NF)以及第三正規化(3NF)。隨後又出現了更高級別的正規化,包括 BC 正規化(BCNF)、第四正規化(4NF)等。每個正規化都基於前面的正規化,例如第二正規化需要先滿足第一正規化。它們之間的關係如下圖所示:

下面我們對正規化進行具體的分析,對於大多數的資料庫系統而言,到達第三正規化就已經足夠了。

第一正規化:

第一正規化要求滿足以下條件:

  • 表中的欄位都是不可再分的單一屬性;
  • 表需要定義主鍵(PRIMARY KEY);

簡單來說,首先就是每個屬性要有單獨的欄位。在上面的不規範設計中,員工的居住地址和手機號都儲存在一個欄位中,破壞了原子性。另外,還需要為表定義一個主鍵,用於唯一識別表中的每一行資料;假設每個部門中的員工不會同名,可以使用部門名稱加員工姓名作為聯合主鍵。

將上面的示例修改成以下結構就可以滿足第一正規化:

這裡我們將原來的"居住資訊"拆分成了兩個欄位,因為員工的手機號和住址是相互獨立的;此外我們將"部門名稱"和"姓名"作為了聯合主鍵(假設不重複),當然更常見的做法是使用資料庫的自增主鍵。

第二正規化:

第二正規化要求滿足以下條件:

  • 滿足第一正規化;
  • 非主鍵欄位必須完全依賴於主鍵, 不能只依賴於主鍵的一部分(不能存在部分函式依賴);

A和B能夠確定C,但是單獨的A或B不能確定C,此時C完全依賴於(A, B);A和B能夠確定C,但是單獨的A或B也能確定C,那麼C部分依賴於(A, B)。而在第二正規化中,非主鍵欄位必須要完全依賴於主鍵。

示例中的"部門地址"只取決於"部門名稱"、也就是主鍵的一部分,它和"姓名無關",因此出現了部分函式依賴。顯然,此時部門資訊存在冗餘,可能帶來各種操作異常。

此時,可以將部門資訊和職位資訊儲存到別的表中,並通過外來鍵讓它們和員工表之間建立一對多的關係。我們可以繼續將表的結構修改如下:

第三正規化:

第三正規化要求滿足以下條件:

  • 滿足第二正規化;
  • 屬性不依賴於其它的非主鍵屬性(不能存在傳遞函式依賴);

怎麼理解呢?如果通過A能夠確定B,通過B能夠確定C,但是通過C不能確定A,那麼C的傳遞依賴於A。

對於前三個正規化而言,只需要將不同的實體/物件單獨儲存到一張表中,並且通過外來鍵建立它們之間的聯絡即可滿足。

此時,我們再來看看非規範化設計時的幾個問題,會發現都得到了解決:

  • 部門、員工以及職位資訊分別儲存一份,通過外來鍵保持它們之間的聯絡。因此,不存在資料冗餘的問題
  • 如果想要成立一個新的部門,直接錄入部門資訊即可,解決了插入異常的問題
  • 如果某個部門的所有員工都被刪除,該部門的資訊不會受到影響,不存在刪除異常
  • 如果需要修改部門資訊,直接更新部門表即可,不會導致資料不一致

很多不搞大資料的公司可能要求在設計表的時候,必須嚴格符合第三正規化,但是搞大資料的話,其實是不需要遵循第三正規化的。因為搞大資料重點是在數倉的建設,分好層才是最重要的,另外如果在數倉建設中嚴格遵循第三正規化的話,那麼需要很多的外來鍵,而為了維護外來鍵需要額外的效能。但如果不用外來鍵的話,在查詢的時候就又會出現大量的join。

關係建模和維度建模

當今的資料處理可以分為兩大類:聯機事務處理(OLTP, on-line transaction processing)和聯機分析處理(OLAP, on-line analytical processing)。OLTP是傳統的關係型資料庫的主要應用,主要進行基本的、日常的事務處理,比如銀行交易;OLAP是數倉系統的主要應用,支援複雜的分析操作,側重決策支援,並且提供直觀易懂的查詢結果。

把OLTP想象成PostgreSQL,OLAP想象成Hive即可。一般搭建資料倉庫,所面臨的場景都是資料量達到關係型資料庫不好儲存了(當然即便資料量不大,也是可以使用數倉的,把關係型資料庫本身當成數倉也行),而且對於數倉來說,它的重點在於大批量資料查詢分析,不在於資料的匯入。所以OLAP不可能像OLTP那樣,來一條資料就插入一條,而是要批量匯入的。另外Hive的底層儲存使用的HDFS,從HDFS的設計原理來講,我們也能看出它不適合少量資料的頻繁匯入,而是一次性匯入大批量資料。

關係建模:

關係模型是要遵循第三正規化的,通過拆分成多個物理表,來降低資料的冗餘度。由於資料分佈在眾多的表中,這些資料可以更加靈活的背應用,功能性更強。關係模型主要應用在OLTP系統中,為了保證資料的一致性以及避免資料冗餘,所以大部分業務系統的表都是遵循第三正規化的。

維度建模:

維度模型不需要遵循第三正規化,它主要應用於OLAP系統中,通常是以某一個事實表為中心進行表的組織,主要面向業務。特徵是存在資料冗餘,但是能最方便地得到資料。

關係模型雖然資料冗餘度低,但是在大規模資料的跨表分析和統計查詢的過程中,會造成多表關聯,會導致執行效率的大幅度降低。而數倉主要在於查詢,所以我們會採用維度建模,把相關的表分為兩種:維度表和事實表。

而在維度建模的基礎上又分為三種模型:星型模型、雪花模型、星座模型。

星型模型:

事實表周圍便是維度表(關於兩者的區別後面會說),星型模型要求"事實表"周圍的"維度表"只能有一層。

雪花模型:

雪花模型的話,事實表周圍的維度表可以有多層。可以看到雪花模型比較靠近第三正規化,但是無法完全遵守。因為對於數倉建設來說,完全遵守第三正規化的成本太高,原因就在於join。

星座模型:

星座模型和前兩個模型的區別就在於事實表的數量,星座模型基於多個事實表,顯然這才是數倉的常態。因為數倉肯定不只有一個事實表,並且維度表是可以被多個事實表共享的。比如上圖:圖中的事實表可以認為是員工參與辯論賽的一張表,有比賽等級(省級、市級、縣級等等),論文標題,參與身份(一個團隊參賽、該員工扮演的角色)。而它周圍的維表顯然是員工的一些個人資訊,這些個人資訊顯然還會被其它的表所需要,因此很好理解。

模型選擇:

模型有以上三種,那麼實際建設中要選擇哪一種呢?

首先星座模型只跟資料和需求有關係,跟設計無關,這個不用選擇。如果維度表被多個事實表需要,那麼就是星座模型;否則就不是星座模型,但是絕大部分都是星座模型,因為不可能就只有一張事實表。所以我們看到星座模型和剩餘兩種模型之間是不衝突的,要麼是星座模型+星型模型,要麼是星座模型+雪花模型。

因此我們的重心是在星型模型和雪花模型的選擇上,至於這兩種模型選擇哪種,則取決於效能優先,還是靈活優先。

目前實際企業開發中,不會絕對選擇一種,而是根據情況靈活組合,甚至並存。但是從整體來看,更傾向維度更少的星型模型。尤其是Hadoop體系,減少join就是減少shuffle,而shuffle是一個性能非常低下的操作。而很多公司用的也正是Hadoop體系,比如Hive,本質上是對MapReduce進行了封裝。

另外,OLAP大部分都是基於Hive、Spark,但是實際上還有很多其它更加優秀的框架,比如:kylin、presto等等,它們的表現更加優秀,只不過Hive、Spark出現的要早一些。但是,大人食大便啦。

Apache Kylin是一個開源的、分散式的分析型資料倉庫,提供Hadoop/Spark 之上的 SQL 查詢介面及多維分析(OLAP)能力以支援超大規模資料,最初由 eBay 開發並貢獻至開源社群。它能在亞秒內查詢巨大的表。

而Presto是由 Facebook 推出的一個基於Java(就很煩)開發的開源分散式SQL查詢引擎,適用於互動式分析查詢,資料量支援GB到PB位元組。

我們有機會慢慢介紹。

維度表和事實表

維度表

維度表:一般是對事實的描述資訊,每一張維度表對應現實世界中的一個物件或者概念。例如:使用者、商品名、日期、地區等等,所以維度表描述是靜態資訊。

維度表的特徵:

  • 維表是一張寬表(具有多個屬性、列比較多)
  • 跟事實表相比,行數相對較小,通常小於10萬條。比如公司的人員資訊表就是一張維度表,顯然公司很少會超過10萬人。
  • 內容相對固定,不是很容易變,因為是靜態資訊。比如人員表,儲存人員的姓名、年齡、工號、手機號等等;比如商品表,存放商品的名稱、價格、描述、功能等等。這些資訊基本上是固定的,當然不是一直不變,而是變化頻率很低。再比如時間表,我們為了避免計算,可能把某個日期是星期幾、一年當中的第幾天、一年當中的第幾周、哪個季度、是否是週六日等等也會儲存起來,而這種表一旦匯入就不會再更改了。

所以,維度表儲存的是那些會被其它表所引用的、不會常變的靜態資訊。

事實表

事實表:裡面的一條資料都代表一個業務事件(下單、支付、退款、評價等等),"事實"這個術語表示的是業務事件的"度量值"(可統計次數、個數、金額等等),所以訂單表便是一張事實表。

每一個事實表的行包括:具有度量值、與維表相關聯的欄位(比如: 通過user_id和使用者表關聯)

事實表的特徵:

  • 非常的大
  • 內容相對的窄(列數較少)
  • 經常會發生變化,每天會新增加很多。

而事實表又分為以下三種:

事務型事實表:以每個事務或者事件為單位,例如一個銷售訂單記錄、一個支付記錄,便是事實表中的一行資料;一旦事務被提交,資料進入到事實表中,那麼事實表中的資料就不能再修改了,其更新方式為增量更新。

週期型快照事實表:週期型快照事實表不會保留所有資料,只保留固定時間間隔的資料,例如每天或者每月的銷售額、賬戶餘額等等。

累計型快照事實表:累計型快照事實表用於跟蹤業務事實的變化。例如:訂單查詢,我們在購買商品的時候,商品郵寄到達了什麼地方都會動態更新。顯然當這個業務進行時,事實表的記錄也要不斷更新或者插入。再比如工作中執行任務,當任務下發的時候,任務狀態為"任務下發";當員工接收任務時,任務狀態為"任務處理中";當員工完成任務之後上報時,任務狀態為"任務已完成"。所以累計型快照事實表它負責跟蹤一個業務的整個生命週期,每當狀態發生更改,就會修改表記錄、或者新增一條記錄。

資料倉庫建模

ODS層

  • 1. 保持資料原貌, 不做任何修改, 只起到備份的作用;
  • 2. 資料採用壓縮, 減少磁碟使用;
  • 3. 建立分割槽表, 每一天的資料是一個單獨的資料夾, 防止後續使用where進行全表掃描;

DWD層

DWD層算是數倉中最重要的一層了,需要構建維度模型。一般採用星型模型,呈現的狀態一般為星座模型。

而維度建模一般採用以下四個步驟:選擇業務過程、宣告粒度、確認維度、確認事實。

1. 選擇業務過程

在業務系統中,挑選我們後續需要關注的業務線,比如:下單業務、支付業務、退款業務、物流業務等等,一條業務線對應一張事實表。

2. 宣告粒度

資料粒度指資料倉庫中儲存的資料的細化程度或者綜合程度的級別。

宣告粒度意味著精確定義事實表中的一行資料表示什麼,應該儘可能選擇最小粒度,依次來滿足各種各樣的需求。

比如:

  • 訂單事實表中每個商品項作為一行資料, 那麼粒度就是"每次下單";
  • 每週的訂單次數作為一行, 粒度就是"每週下單";
  • 每月的訂單次數作為一行, 粒度就是"每月下單";

3. 確定維度

維度的主要作用是描述業務,主要表示"誰、何處、何時"等資訊。

4. 確定事實

此處的"事實"一詞,指的是業務中的度量值,例如訂單金額、下單次數等等。

在DWD層,以"業務驅動"為建模驅動,基於每個具體業務過程的特點,構建最細粒度的明細層事實表,必要情況下可適當寬表化。

還是以電商為例:

橫座標是維度,縱座標是事實表,不同的事實表會依賴同一張維度表。

至此數倉的建模已經完畢,DWS、DWT、ADS和維度建模已經沒有關係了。

而DWS、DWT都是建寬表,而建寬表則按照主題去建,主題相當於觀察問題的角度,對應著維度表。比如:圖中有八張事實表,我想檢視哪些商品被加入到購物車裡面了、哪些商品被評價了、哪些商品被退款了等等,這裡的商品便是主題。

DWS層

統計各個主題物件的當天行為,服務於DWT層的主題寬表,以及一些業務明細資料,應對特殊需求(例如:購買行為,統計商品復購率)。

DWT層

以分析的主題物件為建模驅動,基於上層應用和產品的指標需求,構建主題物件的全量寬表。

ADS層

對各大主題指標分別進行分析。