MySQL進階系列:資料庫設計中的正規化究竟該如何使用
“這篇文章主要為了說明規矩要遵守,但是也別這麼死板,要知道因場景不同而變化。了解各自的優缺點,在不同業務中根據需求選擇使用。”
我們在項目上進行數據庫設計的時候要求遵守三範式,為什麼會約束三範式呢:為了減少數據冗餘。
回憶下是哪三範式:
-
所有屬性具有原子性,列不可分割。
例如家庭地址(xx省xx市xx地址),家庭地址作為字段就是非原子的,可以拆分成字段省份,城市,地址。
-
在第一範式的基礎上,要求所有非主鍵字段完全依賴主鍵,不能產生部分依賴。
一個數據庫表中,一個表中只能保存一種數據,不可以把多種數據保存在同一張數據庫表中。
-
在第二範式的基礎上,保證每列都和主鍵直接相關,不存在傳遞依賴
表中的字段和主鍵直接對應不依靠其他中間字段。傳遞依賴:A--->B--->C。
範式
優點:
-
範式化的更新通常比反範式更快。
-
當數據較好的範式化後,就只有很少或者沒有重復數據。
-
範式化的表通常更小,可以更好的放進內存了,所以執行操作也會更快。
-
很少有多餘的數據意味著檢索列表數據時更少需要distinct和group by語句。
缺點:
-
通常需要表關聯,復雜一點的查詢語句可能至少需要一次關聯,也可能會使得索引失效。
阿裡開發手冊中規定表join關聯不能超過3個,主要原因就是數據量大的時候join查詢非常慢,但是也不一定不能關聯多個,具體問題具體分析,數據量少的時候多張表關聯也沒影響的。
反範式
優點:
-
數據都在一張表中,可以很好的避免關聯。
如果不需要關聯,則對大部分查詢最差的情況---即使表沒有使用索引,是全表掃描。當數據比內存大時,可能比關聯要快得多,因為這樣避免了隨機I/O(全表掃描基本上是順序I/O,但不是100%的和引擎有關).
-
單表可以更有效的使用索引策略。
缺點:
-
表中的冗餘較多,刪除數據的時候容易造成部分有用數據丟失。
混用範式和反範式
實際上完全範式或者完全反範式都是理論上的。在實際的項目開發中,基本都是混用的,沒有嚴格的規定。
案例分析:
例A: 假設有一個網站,允許用戶發送消息,而且其中一些用戶是VIP,現在想查看VIP用戶的近10條信息。
-
完全範式化 表設計:user(user_id,user_type)表和message(message_id,user_id,message_text,published)表,published構建索引
查詢sql:
SELECT message.message_textFROM message INNER JOIN USER ON message.user_id = USER.user_idWHERE USER.user_type = 'VIP' ORDER BY message.published DESC LIMIT 10;
上面sql需要表關聯,mysql需要掃描message 表的日期published的索引,對於每一行找到的數據都要到user表檢索是不是VIP用戶,如果VIP只是很小的一部分,這個效率就很低下了。另一種執行計劃是先從user表開始,找所有VIP用戶獲取並排序,這種可能更糟糕。
2. 完全的反範式,需要在message表中存儲user數據,就會存在message數據操作影響user數據的問題。
3. 混用範式和反範式:修改message表結構增加用戶類型字段user_type, 如:message(message_id,user_id,message_text,published,user_type),這種設計可以避免完全範式化帶來的表關聯查詢,也避免了完全反範式的插入刪除問題(即使沒有消息用戶的信息也不會丟失)。
例B: 如果部分需求是查詢的結果需要排序,從父表中冗餘一些數據到子表更方便設計索引,提高查詢效率。
例C: 對於緩存衍生值也是有效的,如果需要顯示每個用戶發了多少消息(論壇發帖),每次需要執行一個統計的自查詢計算,其實可以在user表中增加消息數量的字段,當用戶發送消息的時候更新這個值(需要平衡更新和查詢哪個更好)。
以上只是為了說明範式和反範式以及混用範式而舉的例子,但是實際開發中還是要根據業務來選擇怎麼使用。
在表設計中,使用範式也好,反範式也好,不應該有嚴格的限制,該用哪種就使用哪種或者兩者結合使用。