寫在前面
爲了解決數據庫層的擴展問題,咱們已經討論了兩種方案:數據庫
Replication:從單庫擴展到多庫,以承載更多的請求量設計模式
Partitioning:把單庫(表)拆分紅多庫(表),打破單庫的性能瓶頸性能優化
在(多機)多庫多表的加持下,激增的請求量、數據量已經再也不是難題,然而,除卻數據量外,還有一個極其影響單庫性能的因素——數據的組織方式數據庫設計
例如,在關係型數據庫中,數據實體用二維表格(稱爲實體表)來描述:ide
實體之間的複雜關聯關係(多對多)也經過二維表格(稱爲關係表)來描述:性能
於是常常須要多表聯查才能獲得目標信息,關係越複雜,讀取性能越差,並最終像數據量同樣成爲單庫性能瓶頸,制約着數據庫層的可擴展性優化
那麼,對於關係型數據庫,有辦法進一步提高數據讀取性能嗎?ui
有,(在必定程度上)改變數據的組織方式,即反範式化(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).
例如:
3NF
以至於常常須要多表聯查(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)來保證冗餘數據的一致性,但這些規則又會抵消一部分做用
參考資料
Denormalization
Database normalization
DB2 Developer’s Guide-Fourth Edition
Database — Design: Logical Design (Part 6)