寫在前面
為了解決資料庫層的擴充套件問題,我們已經討論了兩種方案:
-
Replication:從單庫擴充套件到多庫,以承載更多的請求量
-
Partitioning:把單庫(表)拆分成多庫(表),打破單庫的性能瓶頸
在(多機)多庫多表的加持下,激增的請求量、資料量已經不再是難題,然而,除卻資料量外,還有一個極其影響單庫性能的因素——資料的組織方式
例如,在關係型資料庫中,資料實體用二維表格(稱為實體表)來描述:

實體之間的複雜關聯關係(多對多)也通過二維表格(稱為關係表)來描述:

因而經常需要多表聯查才能得到目標資訊,關係越複雜,讀取性能越差,並最終像資料量一樣成為單庫���能瓶頸,制約著資料庫層的可擴充套件性
那麼,對於關係型資料庫,有辦法進一步提升資料讀取性能嗎?
有,(在一定程度上)改變資料的組織方式,即反範式化(Denormalization)
一。範式化
在討論反範式化之前,有必要先明確什麼是範式化,要反的東西是什麼?
Database normalization is the process of structuring a relational database in accordance with a series of so-called normal forms in order to reduce data redundancy and improve data integrity.
範式化(Database normalization),就是按照一系列範式(Normal forms)要求來組織資料模型的過程,目的是減少資料冗餘,提高 資料完整性
試想,如果相同的資訊在多行中重複出現,不相干的資訊也湊在同一張表中,就很容易出現一些異常情況:
-
更新異常:只更新單行,就會出現邏輯上的不一致
-
插入異常:無法只插入部分資訊,除非讓其它列先留空
-
刪除異常:刪除部分資訊的同時,可能會波及其它無關資訊
為了避免這些異常情況,人們提出了一些約束規則,即資料庫設計範式
二。資料庫設計範式
-
1NF:第一範式(First normal form) 要求資料表中每個欄位的值都不可再分
-
2NF:第二範式(Second normal form) 在滿足 1NF 的基礎上,要求所有非主屬性都完全依賴於其主鍵
-
3NF:第三範式(Third normal form) 在滿足 2NF 的基礎上,要求所有非主屬性都不傳遞依賴於任何主鍵
P.S. 此外,還有 BCNF、4NF、5NF 等等,具體見 Normal forms
類比應用層,設計範式相當於資料層的設計模式,對資料表進行解耦,使單表資訊更加內聚,彼此邊界分明,依賴關係更加清晰
我們一般把滿足 3NF 的關係模式(Relation schema)稱為規範化的(Normalized),大多數情況下都能規避上面提到的插入、更新和刪除異常。然而,在解決這些問題的同時,範式化也帶來了另一些問題
三。範式化的弊端
在這些設計範式的約束下,相關聯的資訊被儲存到了不同的邏輯表中:
A normalized design will often "store" different but related pieces of information in separate logical tables (called relations).
例如:
[caption id="attachment_2131" align="alignnone" width="625"]
3NF[/caption]
以至於經常需要多表聯查(join 操作),關係越複雜,連表查詢越慢:
If these relations are stored physically as separate disk files, completing a database query that draws information from several relations (a join operation) can be slow. If many relations are joined, it may be prohibitively slow.
那麼,有辦法能改善查詢性能嗎?
有。引入冗餘:
-
允許 DBMS 儲存額外的冗餘資訊,例如索引檢視(indexed views)、物化檢視(materialized views),但仍遵從設計範式
-
增加冗餘資料,減少
join操作,打破設計範式(即反範式化)
四。反範式化
所謂反範式化,是一種針對遵從設計範式的資料庫(關係模式)的性能優化策略:
Denormalization is a strategy used on a previously-normalized database to increase performance.
P.S. 注意,反範式化不等於非範式化(Unnormalized form),反範式化一定發生在滿足範式設計的基礎之上。前者相當於先遵守所有規則,再進行局部調整,故意打破一些規則,而後者全然不顧規則
通過增加冗餘資料或對資料進行分組,犧牲一部分寫入性能,換取更高的讀取性能:
In computing, denormalization is the process of trying to improve the read performance of a database, at the expense of losing some write performance, by adding redundant copies of data or by grouping data.
在設計範式的約束下,資料表中沒有冗餘資訊(某個資料只存放在某張表的某個儲存格中),為了得到某個資料可能需要一系列的跨表查詢,因而讀操作性能不佳,但寫操作很快,因為更新資料時只需要修改一處
反範式化就是要打破這種約束,把某些資料在不同的地方多放幾份,以加快資料檢索速度:
The opposite of normalization, denormalization is the process of putting one fact in many places.
具體操作
具體地,常見做法如:
-
存一些派生資料:類似於往 Redux Store 中塞計算屬性,把需要頻繁重複計算的結果存起來,例如在一對多關係中,把「多」的數量作為「一」的屬性儲存起來
-
預先連線(pre-joined)生成彙總表:把需要頻繁
join的表提前join好 -
採用硬編碼值:把依賴表中的常量值(或者不經常變化的值)直接硬編碼到當前表中,從而避免
join操作 -
把詳情資訊納入主表中:對於資料量不大的詳情表,可以把全部/部分詳情資訊塞到主表中,以避免
join操作
P.S. 關於反範式化具體做法的更多資訊,見 When and How You Should Denormalize a Relational Database
五。反範式化的代價
但除非必要,一般不建議反範式化,因其代價高昂:
-
失去了資料完整性保障:打破範式,意味著之前通過範式化解決的更新、插入、刪除異常問題又將重新冒出來,也就是說,冗餘資料的一致性要靠 DBA 自己來保證,而不像索引檢視等由 DBMS 來保證
-
犧牲了寫入速度:由於反範式化引入了冗餘資料,更新時要修改多處,但大多數場景都是讀密集的,寫入慢一點問題不大
-
浪費了儲存空間:儲存了不必要的冗餘資料,自然會浪費一些儲存空間,但空間換時間一般是可接受的(畢竟記憶體、硬碟等資源已經相對廉價了)
P.S. 一般通過約束規則(constraints)來保證冗餘資料的一致性,但這些規則又會抵消一部分作用
暫無評論,快來發表你的看法吧