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