MongoDB數(shù)據(jù)庫(kù)設(shè)計(jì)中6條重要的經(jīng)驗(yàn)法則

Part 1

原文:6 Rules of Thumb for MongoDB Schema Design: Part 1

By William Zola, Lead Technical Support Engineer at MongoDB

“我有豐富的sql使用經(jīng)驗(yàn)膛堤,但是我是個(gè)MongoDB的初學(xué)者踪少。我應(yīng)該如何在MongoDB中針對(duì)一對(duì)多關(guān)系進(jìn)行建模?”這是我被問及最多的問題之一瓢颅。

我沒法簡(jiǎn)單的給出答案,因?yàn)檫@有很多方案去實(shí)現(xiàn)。接下來我會(huì)教導(dǎo)你如何針對(duì)一對(duì)多進(jìn)行建模。

這個(gè)話題有很多內(nèi)容需要討論社牲,我會(huì)用三個(gè)部分進(jìn)行說明。在第一部分悴了,我會(huì)討論針對(duì)一對(duì)多關(guān)系建模的三種基礎(chǔ)方案搏恤。在第二部分我將會(huì)覆蓋更多高級(jí)內(nèi)容,包括反范式化和雙向引用湃交。在最后一部分熟空,我將會(huì)回顧各種選擇,并給出做決定時(shí)需要考慮的因素搞莺。

很多初學(xué)者認(rèn)為在MongoDB中針對(duì)一對(duì)多建模唯一的方案就是在父文檔中內(nèi)嵌一個(gè)數(shù)組子文檔息罗,但是這是不準(zhǔn)確的。因?yàn)槟憧梢栽贛ongoDB內(nèi)嵌一個(gè)文檔不代表你就必須這么做才沧。

當(dāng)你設(shè)計(jì)一個(gè)MongoDB數(shù)據(jù)庫(kù)結(jié)構(gòu)迈喉,你需要先問自己一個(gè)在使用關(guān)系型數(shù)據(jù)庫(kù)時(shí)不會(huì)考慮的問題:這個(gè)關(guān)系中集合的大小是什么樣的規(guī)模?你需要意識(shí)到一對(duì)很少温圆,一對(duì)許多挨摸,一對(duì)非常多,這些細(xì)微的區(qū)別岁歉。不同的情況下你的建模也將不同得运。

Basics: Modeling One-to-Few

一對(duì)很少

針對(duì)個(gè)人需要保存多個(gè)地址進(jìn)行建模的場(chǎng)景下使用內(nèi)嵌文檔是很合適,可以在person文檔中嵌入addresses數(shù)組文檔:

image

這種設(shè)計(jì)具有內(nèi)嵌文檔設(shè)計(jì)中所有的優(yōu)缺點(diǎn)。最主要的優(yōu)點(diǎn)就是不需要單獨(dú)執(zhí)行一條語(yǔ)句去獲取內(nèi)嵌的內(nèi)容澈圈。最主要的缺點(diǎn)是你無法把這些內(nèi)嵌文檔當(dāng)做單獨(dú)的實(shí)體去訪問彬檀。

例如帆啃,如果你是在對(duì)一個(gè)任務(wù)跟蹤系統(tǒng)進(jìn)行建模瞬女,每個(gè)用戶將會(huì)被分配若干個(gè)任務(wù)。內(nèi)嵌這些任務(wù)到用戶文檔在遇到“查詢昨天所有的任務(wù)”這樣的問題時(shí)將會(huì)非常困難努潘。我會(huì)在下一篇文章針對(duì)這個(gè)用例提供一些適當(dāng)?shù)脑O(shè)計(jì)诽偷。

Basics: One-to-Many

一對(duì)許多

以產(chǎn)品零件訂貨系統(tǒng)為例。每個(gè)商品有數(shù)百個(gè)可替換的零件疯坤,但是不會(huì)超過數(shù)千個(gè)报慕。這個(gè)用例很適合使用間接引用---將零件的objectid作為數(shù)組存放在商品文檔中(在這個(gè)例子中的ObjectID我使用更加易讀的2字節(jié),現(xiàn)實(shí)世界中他們可能是由12個(gè)字節(jié)組成的)压怠。

每個(gè)零件都將有他們自己的文檔對(duì)象

image

每個(gè)產(chǎn)品的文檔對(duì)象中parts數(shù)組中將會(huì)存放多個(gè)零件的ObjectID :

image

在獲取特定產(chǎn)品中所有零件眠冈,需要一個(gè)應(yīng)用層級(jí)別的join

為了能快速的執(zhí)行查詢,必須確保products.catalog_number有索引菌瘫。當(dāng)然由于零件中parts._id一定是有索引的蜗顽,所以這也會(huì)很高效。

這種引用的方式是對(duì)內(nèi)嵌優(yōu)缺點(diǎn)的補(bǔ)充雨让。每個(gè)零件是個(gè)單獨(dú)的文檔雇盖,可以很容易的獨(dú)立去搜索和更新他們。需要一條單獨(dú)的語(yǔ)句去獲取零件的具體內(nèi)容是使用這種建模方式需要考慮的一個(gè)問題(請(qǐng)仔細(xì)思考這個(gè)問題栖忠,在第二章反反范式化中崔挖,我們還會(huì)討論這個(gè)問題)

這種建模方式中的零件部分可以被多個(gè)產(chǎn)品使用,所以在多對(duì)多時(shí)不需要一張單獨(dú)的連接表庵寞。

Basics: One-to-Squillions

一對(duì)非常多

我們用一個(gè)收集各種機(jī)器日志的例子來討論一對(duì)非常多的問題狸相。由于每個(gè)mongodb的文檔有16M的大小限制,所以即使你是存儲(chǔ)ObjectID也是不夠的捐川。我們可以使用很經(jīng)典的處理方法“父級(jí)引用”---用一個(gè)文檔存儲(chǔ)主機(jī)脓鹃,在每個(gè)日志文檔中保存這個(gè)主機(jī)的ObjectID。

image

以下是個(gè)和第二中方案稍微不同的應(yīng)用級(jí)別的join用來查找一臺(tái)主機(jī)最近5000條的日志信息

image

所以属拾,即使這種簡(jiǎn)單的討論也有能察覺出mongobd的建模和關(guān)系模型建模的不同之處将谊。你必須要注意一下兩個(gè)因素:

Will the entities on the “N” side of the One-to-N ever need to stand alone?

一對(duì)多中的多是否需要一個(gè)單獨(dú)的實(shí)體。

What is the cardinality of the relationship: is it one-to-few; one-to-many; or one-to-squillions?

這個(gè)關(guān)系中集合的規(guī)模是一對(duì)很少渐白,很多尊浓,還是非常多。

Based on these factors, you can pick one of the three basic One-to-N schema designs:

基于以上因素來決定采取一下三種建模的方式

一對(duì)很少且不需要單獨(dú)訪問內(nèi)嵌內(nèi)容的情況下可以使用內(nèi)嵌多的一方纯衍。

一對(duì)多且多的一端內(nèi)容因?yàn)楦鞣N理由需要單獨(dú)存在的情況下可以通過數(shù)組的方式引用多的一方的栋齿。

一對(duì)非常多的情況下,請(qǐng)將一的那端引用嵌入進(jìn)多的一端對(duì)象中。

Part 2

原文:6 Rules of Thumb for MongoDB Schema Design: Part 2

By William Zola, Lead Technical Support Engineer at MongoDB

在上一篇文章中我介紹了三種基本的設(shè)計(jì)方案:內(nèi)嵌瓦堵,子引用基协,父引用,同時(shí)說明了在選擇方案時(shí)需要考慮的兩個(gè)關(guān)鍵因素菇用。

一對(duì)多中的多是否需要一個(gè)單獨(dú)的實(shí)體澜驮。

這個(gè)關(guān)系中集合的規(guī)模是一對(duì)很少,很多惋鸥,還是非常多杂穷。

在掌握了以上基礎(chǔ)技術(shù)后,我將會(huì)介紹更為高級(jí)的主題:雙向關(guān)聯(lián)和反范式化卦绣。

雙向關(guān)聯(lián)

如果你想讓你的設(shè)計(jì)更酷耐量,你可以讓引用的“one”端和“many”端同時(shí)保存對(duì)方的引用。

以上一篇文章討論過的任務(wù)跟蹤系統(tǒng)為例滤港。有person和task兩個(gè)集合廊蜒,one-to-n的關(guān)系是從person端到task端。在需要獲取person所有的task這個(gè)場(chǎng)景下需要在person這個(gè)對(duì)象中保存有task的id數(shù)組溅漾,如下面代碼所示山叮。

image

在某些場(chǎng)景中這個(gè)應(yīng)用需要顯示任務(wù)的列表(例如顯示一個(gè)多人協(xié)作項(xiàng)目中所有的任務(wù)),為了能夠快速的獲取某個(gè)用戶負(fù)責(zé)的項(xiàng)目可以在task對(duì)象中嵌入附加的person引用關(guān)系樟凄。

image

這個(gè)方案具有所有的一對(duì)多方案的優(yōu)缺點(diǎn)聘芜,但是通過添加附加的引用關(guān)系。在task文檔對(duì)象中添加額外的“owner”引用可以很快的找到某個(gè)task的所有者缝龄,但是如果想將一個(gè)task分配給其他person就需要更新引用中的person和task這兩個(gè)對(duì)象(熟悉關(guān)系數(shù)據(jù)庫(kù)的童鞋會(huì)發(fā)現(xiàn)這樣就沒法保證操作的原子性汰现。當(dāng)然,這對(duì)任務(wù)跟蹤系統(tǒng)來說并沒有什么問題叔壤,但是你必須考慮你的用例是否能夠容忍)

在一對(duì)多關(guān)系中應(yīng)用反范式

在你的設(shè)計(jì)中加入反范式瞎饲,可以使你避免應(yīng)用層級(jí)別的join讀取,當(dāng)然炼绘,代價(jià)是這也會(huì)讓你在更新是需要操作更多數(shù)據(jù)嗅战。下面我會(huì)舉個(gè)例子來進(jìn)行說明

反范式Many -< One

以產(chǎn)品和零件為例,你可以在parts數(shù)組中冗余存儲(chǔ)零件的名字俺亮。以下是沒有加入反范式設(shè)計(jì)的結(jié)構(gòu)驮捍。

image

反范式化意味著你不需要執(zhí)行一個(gè)應(yīng)用層級(jí)別的join去顯示一個(gè)產(chǎn)品所有的零件名字,當(dāng)然如果你同時(shí)還需要其他零件信息那這個(gè)應(yīng)用層的join是避免不了的脚曾。

image

在使得獲取零件名字簡(jiǎn)單的同時(shí)东且,執(zhí)行一個(gè)應(yīng)用層級(jí)別的join會(huì)和之前的代碼有些區(qū)別,具體如下:

image

反范式化在節(jié)省你讀的代價(jià)的同時(shí)會(huì)帶來更新的代價(jià):如果你將零件的名字冗余到產(chǎn)品的文檔對(duì)象中本讥,那么你想更改某個(gè)零件的名字你就必須同時(shí)更新所有包含這個(gè)零件的產(chǎn)品對(duì)象珊泳。

在一個(gè)讀比寫頻率高的多的系統(tǒng)里鲁冯,反范式是有使用的意義的。如果你很經(jīng)常的需要高效的讀取冗余的數(shù)據(jù)色查,但是幾乎不去變更他d話薯演,那么付出更新上的代價(jià)還是值得的。更新的頻率越高秧了,這種設(shè)計(jì)方案的帶來的好處越少跨扮。

例如:假設(shè)零件的名字變化的頻率很低,但是零件的庫(kù)存變化很頻繁示惊,那么你可以冗余零件的名字到產(chǎn)品對(duì)象中好港,但是別冗余零件的庫(kù)存。

需要注意的是米罚,一旦你冗余了一個(gè)字段,那么對(duì)于這個(gè)字段的更新將不在是原子的丈探。和上面雙向引用的例子一樣录择,如果你在零件對(duì)象中更新了零件的名字,那么更新產(chǎn)品對(duì)象中保存的名字字段前將會(huì)存在短時(shí)間的不一致碗降。

反范式One -< Many

你也可以冗余one端的數(shù)據(jù)到many端:

image

如果你冗余產(chǎn)品的名字到零件表中隘竭,那么一旦更新產(chǎn)品的名字就必須更新所有和這個(gè)產(chǎn)品有關(guān)的零件,這比起只更新一個(gè)產(chǎn)品對(duì)象來說代價(jià)明顯更大讼渊。這種情況下动看,更應(yīng)該慎重的考慮讀寫頻率。

在一對(duì)很多的關(guān)系中應(yīng)用反范式

在日志系統(tǒng)這個(gè)一對(duì)許多的例子中也可以應(yīng)用反范式化的技術(shù)爪幻。你可以將one端(主機(jī)對(duì)象)冗余到日志對(duì)象中菱皆,或者反之。

下面的例子將主機(jī)中的IP地址冗余到日志對(duì)象中挨稿。

image

如果想獲取最近某個(gè)ip地址的日志信息就變的很簡(jiǎn)單仇轻,只需要一條語(yǔ)句而不是之前的兩條就能完成。

image

事實(shí)上奶甘,如果one端只有少量的信息存儲(chǔ)篷店,你甚至可以全部冗余存儲(chǔ)到多端上,合并兩個(gè)對(duì)象臭家。

image

另一方面疲陕,也可以冗余數(shù)據(jù)到one端。比如說你想在主機(jī)文檔中保存最近的1000條日志钉赁,可以使用mongodb 2.4中新加入的$eache/$slice功能來保證list有序而且只保存1000條蹄殃。

日志對(duì)象保存在logmsg集合中,同時(shí)冗余到hosts對(duì)象中橄霉。這樣即使hosts對(duì)象中超過1000條的數(shù)據(jù)也不會(huì)導(dǎo)致日志對(duì)象丟失窃爷。

image

通過在查詢中使用投影參數(shù) (類似{_id:1})的方式在不需要使用logmsgs數(shù)組的情況下避免獲取整個(gè)mongodb對(duì)象邑蒋,1000個(gè)日志信息帶來的網(wǎng)絡(luò)開銷是很大的。

在一對(duì)多的情況下按厘,需要慎重的考慮讀和更新的頻率医吊。冗余日志信息到主機(jī)文檔對(duì)象中只有在日志對(duì)象幾乎不會(huì)發(fā)生更新的情況下才是個(gè)好的決定。

總結(jié)

在這篇文章里逮京,我介紹了對(duì)三種基礎(chǔ)方案:內(nèi)嵌文檔卿堂,子引用,父引用的補(bǔ)充選擇懒棉。

使用雙向引用來優(yōu)化你的數(shù)據(jù)庫(kù)架構(gòu)草描,前提是你能接受無法原子更新的代價(jià)。

可以在引用關(guān)系中冗余數(shù)據(jù)到one端或者N端策严。

在決定是否采用反范式化時(shí)需要考慮下面的因素:

你將無法對(duì)冗余的數(shù)據(jù)進(jìn)行原子更新穗慕。

只有讀寫比較高的情況下才應(yīng)該采取反范式化的設(shè)計(jì)谓谦。

Part 3

原文:6 Rules of Thumb for MongoDB Schema Design: Part 3

By William Zola, Lead Technical Support Engineer at MongoDB

這篇文章是系列的最后一篇啊犬。在第一篇文章里嫉晶,我介紹了三種針對(duì)“一對(duì)多 ”關(guān)系建模的基礎(chǔ)方案铐然。在第二篇文章中幅垮,我介紹了對(duì)基礎(chǔ)方案的擴(kuò)展:雙向關(guān)聯(lián)和反范式化拱层。

反范式可以讓你避免一些應(yīng)用層級(jí)別的join虹菲,但是這也會(huì)讓更新變的更復(fù)雜憋肖,開銷更大寿酌。不過冗余那些讀取頻率遠(yuǎn)遠(yuǎn)大于更新頻率的字段還是值得的胰苏。

如果你還沒有讀過前兩篇文章,歡迎一覽醇疼。

讓我們回顧下這些方案

你可以采取內(nèi)嵌硕并,或者建立one端或者N端的引用,也可以三者兼而有之僵腺。

你可以在one端或者N端冗余多個(gè)字段

下面這些是你需要謹(jǐn)記的:

1鲤孵、優(yōu)先考慮內(nèi)嵌,除非有什么迫不得已的原因辰如。

2普监、需要單獨(dú)訪問一個(gè)對(duì)象,那這個(gè)對(duì)象就不適合被內(nèi)嵌到其他對(duì)象中琉兜。

3凯正、數(shù)組不應(yīng)該無限制增長(zhǎng)。如果many端有數(shù)百個(gè)文檔對(duì)象就不要去內(nèi)嵌他們可以采用引用ObjectID的方案豌蟋;如果有數(shù)千個(gè)文檔對(duì)象廊散,那么就不要內(nèi)嵌ObjectID的數(shù)組。該采取哪些方案取決于數(shù)組的大小梧疲。

4允睹、不要害怕應(yīng)用層級(jí)別的join:如果索引建的正確并且通過投影條件(第二章提及)限制返回的結(jié)果运准,那么應(yīng)用層級(jí)別的join并不會(huì)比關(guān)系數(shù)據(jù)庫(kù)中join開銷大多少。

5缭受、在進(jìn)行反范式設(shè)計(jì)時(shí)請(qǐng)先確認(rèn)讀寫比胁澳。一個(gè)幾乎不更改只是讀取的字段才適合冗余到其他對(duì)象中。

6米者、在mongodb中如何對(duì)你的數(shù)據(jù)建模韭畸,取決于你的應(yīng)用程序如何去訪問它們。數(shù)據(jù)的結(jié)構(gòu)要去適應(yīng)你的程序的讀寫場(chǎng)景蔓搞。

設(shè)計(jì)指南

當(dāng)你在MongoDB中對(duì)“一對(duì)多”關(guān)系進(jìn)行建模胰丁,你有很多的方案可供選擇,所以你必須很謹(jǐn)慎的去考慮數(shù)據(jù)的結(jié)構(gòu)喂分。下面這些問題是你必須認(rèn)真思考的:

關(guān)系中集合的規(guī)模有多大:是一對(duì)很少锦庸,很多,還是非常多妻顶?

對(duì)于一對(duì)多中”多“的那一端酸员,是否需要單獨(dú)的訪問它們,還是說它們只會(huì)在父對(duì)象的上下文中被訪問讳嘱。

被冗余的字段的讀寫的比例是多少?

數(shù)據(jù)建模設(shè)計(jì)指南

在一對(duì)很少的情況下酿愧,你可以在父文檔中內(nèi)嵌數(shù)組沥潭。

在一對(duì)很多或者需要單獨(dú)訪問“N”端的數(shù)據(jù)時(shí),你可以采用數(shù)組引用ObjectID的方式嬉挡。如果可以加速你的訪問也可以在“N”端使用父引用钝鸽。

在一對(duì)非常多的情況下,可以在“N”端使用父引用庞钢。

如果你打算在你的設(shè)計(jì)中引入冗余等反范式設(shè)計(jì)拔恰,那么你必須確保那些冗余的數(shù)據(jù)讀取的頻率遠(yuǎn)遠(yuǎn)大于更新的頻率。而且你也不需要很強(qiáng)的一致性基括。因?yàn)榉捶妒交脑O(shè)計(jì)會(huì)讓你在更新冗余字段時(shí)付出一定的代價(jià)(更慢颜懊,非原子化)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市风皿,隨后出現(xiàn)的幾起案子河爹,更是在濱河造成了極大的恐慌,老刑警劉巖桐款,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咸这,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡魔眨,警方通過查閱死者的電腦和手機(jī)媳维,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門酿雪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侄刽,你說我怎么就攤上這事指黎。” “怎么了唠梨?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵袋励,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我当叭,道長(zhǎng)茬故,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任蚁鳖,我火速辦了婚禮磺芭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘醉箕。我一直安慰自己钾腺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布讥裤。 她就那樣靜靜地躺著放棒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪己英。 梳的紋絲不亂的頭發(fā)上间螟,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音损肛,去河邊找鬼厢破。 笑死,一個(gè)胖子當(dāng)著我的面吹牛治拿,可吹牛的內(nèi)容都是我干的摩泪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼劫谅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼见坑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起同波,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤鳄梅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后未檩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戴尸,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年冤狡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了孙蒙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片项棠。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖挎峦,靈堂內(nèi)的尸體忽然破棺而出香追,到底是詐尸還是另有隱情,我是刑警寧澤坦胶,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布透典,位于F島的核電站,受9級(jí)特大地震影響顿苇,放射性物質(zhì)發(fā)生泄漏峭咒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一纪岁、第九天 我趴在偏房一處隱蔽的房頂上張望凑队。 院中可真熱鬧,春花似錦幔翰、人聲如沸漩氨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)叫惊。三九已至,卻和暖如春做修,著一層夾襖步出監(jiān)牢的瞬間赋访,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工缓待, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渠牲。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓旋炒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親签杈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瘫镇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容