系統架構設計之數據模型的選型難題
數據模型不僅對軟件編寫方式,還對如何思考待解決的問題都有影響。
大多數應用程序都是通過疊加一層層的數據模型構建而來 。因此每層都面臨問題:如何將其用下一層表示?如:
1. 作為一名開發,觀測現實世界(包括人員、組織 、貨物 、行為、資金流動、傳感器等) ,通過對象或數據結構及操作這些數據結構的API來對其建模,這些數據結構往往特定于該應用
2. 當需要存儲這些數據結構時,可采用通用數據模型(例如JSON XML文檔、關系數據庫中的表或圖模型)來表示
3. DBA接著決定用何種內存、磁盤或網絡的 節格式來表示上述JSON/XML/關系/圖形數據。數據表示需要支持多種方式的查詢詢、搜索、操作和處理數據(系列三時介紹)
4. 在更下一層,硬件工程師則需要考慮用電流、光脈沖、磁場等來表示字節
復雜的應用程序可能會有更多的中間層:如基于API來構建上層API ,但基本思想一樣:每層都通過提供一個簡潔的數據模型來隱藏下層的復雜性。這些抽象機制使得不同團隊可高效協作,例如數據廠商的工程師和使用數據庫的開發很好的協作。
數據模型有很多類型,每種都有其最佳實踐。考慮到數據模型對其上的軟件應用有巨大影響( 哪些可以做、不能做),因此需慎重選擇適合業務的數據模型。
本本會介紹一些用于數據存儲和查詢的通用數據模型 (上面提到的第2點)。尤其,將比較關系模型 、文檔模型和一些基于圖的數據模型。還討論多種查詢語言并比較使用場景。
1.關系模型與文檔模型
如今SQL是最著名的數據模型 ,它基于Edgar Codd 1970年提出的關系模型:數據被組織成關系(relations),在SQL中稱為表(table),其中每個關系都是元組(tuples)的無序集合(SQL中稱為行)。
關系模型曾經只是理論建議, 很多人懷凝它是否能被高效實現。但20世紀80年代中期,關系數據庫管理系統( RDBMS )和SQL已成為大多數需要存儲、查詢具有某種規則結構的數據的首選工具。關系數據庫的主導地位持續至今已有三十多年,算得上是計算機史的不朽傳奇。
關系數據庫核心在于商業數據處理, 20世紀60年代和70年代主要運行在大型計算機。如今來看,用例很常見,主要是:
- 事務處理(包括輸入銷售和銀行交易、訂票 、倉庫庫存 )
- 批處理(例如客戶發票、工資單 、報告
當時的其他數據庫迫使開發人員考慮數據的內部表示。關系模型的目標就是將實現細節隱藏在更簡潔的接口下。20世紀70年代和80代初期,網絡模型和層次模型是兩個主要選擇,但最終關系模型主宰該領域。
對象數據庫曾在20世紀80年代后期和90年代初期起起伏伏。XML數據庫則出現在21世紀初,但也僅限于利基市場。關系模型的競爭者都曇花一現。
如今關系數據庫超出它們最初的商業數據處理范圍,推廣到各種案例:在線發布、論壇、社
交網絡、電子商務 游戲、 SaaS。
2.NoSQL
21世紀,NoSQL成為推翻關系模式主導地位的有力競爭者。NoSQL名字不恰當,因為它其實并不代表具體技術,最初只是作為吸引人眼球的Twitter標簽頻頻出現在2009年的開源、分布式及非關系數據庫的見面會。現在很多新興數據庫系統總是會打上NoSQL標簽,而其含義也已經被逆向重新解釋為“不僅僅是SQL”。
采用NoSQL數據庫的驅動因素:
- 比關系數據庫更好的擴展性需求包括支持超大數據集或超高寫入吞吐量
- 普遍偏愛免費和開源軟件而非商業數據庫產品
- 關系模型不能很好支持一些特定查詢
- 對關系模式某些限制性感到沮喪,渴望更具動態和表達力的數據模型
不同應用程序有不同需求,沒有銀彈技術,可預見的將來,關系數據庫仍將繼續與各種非關系數據存儲一起使用,這種思路有時也被稱為混合持久化。
3.對象-關系不匹配
現在大多數應用開發都采用OOP編程語言 ,由于兼容性問題,普遍對SQL數據模型抱怨:若數據存儲在關系表,則應用層代碼中的對象與表、行和列的數據庫模型之間需要一個笨拙的轉換層。模型之間的脫離有時被稱為阻抗失諧(電子學術語:每個電路的輸入和輸出都有 一定的阻抗(交流電阻) 一個電路的輸出連接到另一個電路的輸入時,若兩個電路的輸出和輸入阻抗匹配,則連接上的功率傳輸將被最大化,阻抗不匹配會導致信號反射和其他故障)。
像Hibernate對象關系映射(ORM) 框架減少了此轉換層的樣板代碼,但也無法完全隱藏兩個模型之間差異。
簡歷案例
如下展示了關系模式表示LinkedIn簡歷:
圖一
整個簡歷可通過唯一標識符user id標識。像first_name、last_name字段在每個用戶中只出現一次,所以建模為users表中的列。而大多數人在他們的職業中有一個以上工作,且可能有多個教育階段和任意數量的聯系信息。用戶與這些項目之間是一對多,可用多種方式表示:
- 在傳統SQL模型( SQL1999之前), 最常見的規范化表示是將職位、教育和聯系信息放在單獨表,并使用外鍵引用users表。
- 之后的SQL標準增加了對結構化數據類型和XML數據的支持。這允許將多值數據存儲在單行內,井支持在這些文檔中查詢和索引。Oracle、DB2、SQLServr、PostgreSQL都不同程度支持這些功能。一些數據庫也支持JSON,如DB2、MySQL和PostgreSQL。
- 將工作、教育和聯系信息編碼為JSON或XML,存儲在數據庫的文本列,并由應用程序解釋其結構和內容。對于此方法,通常不能使用數據庫查詢該編碼列中的值。
簡歷這樣的數據結構,主要是個自包含的文檔( document ),因此用JSON表示非常合適:
面向文檔的數據庫(如MongoDB)都支持該數據模型。
有人覺得JSON模型減少了應用程序代碼和存儲層之間的阻抗失配。然而,JSON作為數據編碼格式也存在問題。缺乏模式常常被認為是一個優勢~
JSON表示比關系模式表示的多表模式具有更好的局部性。若要在關系模式中讀取一份簡歷,則:
? 要么執行多個查詢(通過user_id查詢每個表)
? 要么在users表及其從屬表之間執行混亂的多路聯結
而JSON表示方法,所有的相關信息都在一個地方,一次查詢即可。
用戶簡歷到用戶的職位、教育歷史和聯系信息的 對多關系,意味著數據存在樹狀結構,JSON表示將該樹結構顯式化,一對多的關系形成樹狀結構:
4.多對一、多對多
上面案例的region_id、indu0stry_id 定義為ID ,而非純文本字符串,如"Greater Seattle Area Philanthropy",why?
若用戶界面是可以輸入地區或行業的自由文本字段,則將其存儲為純文本字符串更有意義。但擁有地理區域和行業的標準化列表,并讓用戶從下拉列表或自動填充器中進行選擇更有優勢:
- 所有簡歷保持樣式和輸入值一致。
- 避免歧義(例如,若存在一些同名城市)
- 易于更新:名字只保存一次,因此,若需要改變(例如,由于政治而更改城市名稱),易全面更新。
- 本地化支持:當網站被翻譯成其他語,標準化的列表方便本地化,因此地區和行業可以用查看者的母語顯示。
- 更好的搜索支持:例如,搜索華盛頓州的慈善家可匹配到過個簡歷,因為地區列表可以將西雅圖屬于華盛頓的信息編碼進來(而從“大西雅圖地區”字符串中并不能看出西雅圖屬于華盛頓)。
無論是存儲ID or 文本字符串,都涉及內容重復問題:
- 使用ID ,對人類有意義的信息(例如慈善這個詞)只存儲在一個地方,引用它的所有內容都使用ID(ID只在數據庫中有意義。
- 直接存儲文本時, 則使用它的每條記錄中都保存了一份這樣的可讀信息。
使用ID的好處:對人類沒有任何直接意義,所以永遠不需要直接改變。即使ID標識的信息變化,他也能繼續保持不變。任何對人類有意義的東西都可能變更。若這些信息被復制 ,則所有副本都得更新。這會導致更多寫開銷,且存在數據不一致風險。消除這種重復,也正是數據庫規范化的核心思想。
而這種數據規范化需要表達多對一關系(許多人生活在同一地區, 在同一行業工作),這并不是很適合文檔模型:
- 關系數據庫由于支持聯結操作,可方便通過ID引用其他表中的行
- 文檔數據庫中, 一對多的樹狀結構不需要聯結,支持聯結通常也很弱
若數據庫本身不支持聯結,則必須在應用程序代碼中,通過對數據庫進行多次查詢來模擬聯結(對于上述例子,地區和行業的列表很小且短期內不大可能變化, 應用程序可將其緩存在內存。但無論如何,聯結工作其實從數據庫轉移到了應用層。
即使應用程序的初始版本非常適合采用無聯結 文檔模型 但隨著應用支持越來越多功能,數據也變更互聯一體化。例如,考慮可能對簡歷進行的變更:
- 組織和學校作為實體 前面定義中, organization(用戶所在公司)、school_name (用戶所在學校 )都是字符串。也許他們應該定義為實體的引用?然后每個組織、學校或大學都擁有自己的網頁 (logo 、新聞發布源等) 。每個簡歷都能鏈接到相關組織和學校,包括他們的logo和其他信息
- 推薦假設添加這樣一個新功能:一個用戶能推薦其他用戶。推薦顯示在被推薦者的簡歷上,并附上推薦人的姓名和照片。若推薦人更新了他們的照片, 他們所寫的任何推薦都需顯示新照片。因此,推薦需要有一個到作者簡歷的引用。
如下圖展示了這些新功能如何定義多對多。每個虛線矩形框數據可組織為一個文檔,但指向組織、學校及其他用戶的關系則需表示為引用,并且在查詢時,需要聯結操作。多對多關系來擴展簡歷:
? ?