MongoDB数据库设计中6条重要的经验法则
Part 1
原文:6 Rules of Thumb for MongoDB Schema Design: Part 1
By William Zola, Lead Technical Support Engineer at MongoDB
“我有豐富的sql使用經(jīng)驗,但是我是個MongoDB的初學(xué)者。我應(yīng)該如何在MongoDB中針對一對多關(guān)系進行建模?”這是我被問及最多的問題之一。
我沒法簡單的給出答案,因為這有很多方案去實現(xiàn)。接下來我會教導(dǎo)你如何針對一對多進行建模。
這個話題有很多內(nèi)容需要討論,我會用三個部分進行說明。在第一部分,我會討論針對一對多關(guān)系建模的三種基礎(chǔ)方案。在第二部分我將會覆蓋更多高級內(nèi)容,包括反范式化和雙向引用。在最后一部分,我將會回顧各種選擇,并給出做決定時需要考慮的因素。
很多初學(xué)者認為在MongoDB中針對一對多建模唯一的方案就是在父文檔中內(nèi)嵌一個數(shù)組子文檔,但是這是不準確的。因為你可以在MongoDB內(nèi)嵌一個文檔不代表你就必須這么做。
當你設(shè)計一個MongoDB數(shù)據(jù)庫結(jié)構(gòu),你需要先問自己一個在使用關(guān)系型數(shù)據(jù)庫時不會考慮的問題:這個關(guān)系中集合的大小是什么樣的規(guī)模?你需要意識到一對很少,一對許多,一對非常多,這些細微的區(qū)別。不同的情況下你的建模也將不同。
Basics: Modeling One-to-Few
一對很少
針對個人需要保存多個地址進行建模的場景下使用內(nèi)嵌文檔是很合適,可以在person文檔中嵌入addresses數(shù)組文檔:
這種設(shè)計具有內(nèi)嵌文檔設(shè)計中所有的優(yōu)缺點。最主要的優(yōu)點就是不需要單獨執(zhí)行一條語句去獲取內(nèi)嵌的內(nèi)容。最主要的缺點是你無法把這些內(nèi)嵌文檔當做單獨的實體去訪問。
例如,如果你是在對一個任務(wù)跟蹤系統(tǒng)進行建模,每個用戶將會被分配若干個任務(wù)。內(nèi)嵌這些任務(wù)到用戶文檔在遇到“查詢昨天所有的任務(wù)”這樣的問題時將會非常困難。我會在下一篇文章針對這個用例提供一些適當?shù)脑O(shè)計。
Basics: One-to-Many
一對許多
以產(chǎn)品零件訂貨系統(tǒng)為例。每個商品有數(shù)百個可替換的零件,但是不會超過數(shù)千個。這個用例很適合使用間接引用---將零件的objectid作為數(shù)組存放在商品文檔中(在這個例子中的ObjectID我使用更加易讀的2字節(jié),現(xiàn)實世界中他們可能是由12個字節(jié)組成的)。
每個零件都將有他們自己的文檔對象
?
每個產(chǎn)品的文檔對象中parts數(shù)組中將會存放多個零件的ObjectID?:
?
?
在獲取特定產(chǎn)品中所有零件,需要一個應(yīng)用層級別的join
為了能快速的執(zhí)行查詢,必須確保products.catalog_number有索引。當然由于零件中parts._id一定是有索引的,所以這也會很高效。
這種引用的方式是對內(nèi)嵌優(yōu)缺點的補充。每個零件是個單獨的文檔,可以很容易的獨立去搜索和更新他們。需要一條單獨的語句去獲取零件的具體內(nèi)容是使用這種建模方式需要考慮的一個問題(請仔細思考這個問題,在第二章反反范式化中,我們還會討論這個問題)
這種建模方式中的零件部分可以被多個產(chǎn)品使用,所以在多對多時不需要一張單獨的連接表。
Basics: One-to-Squillions
一對非常多
我們用一個收集各種機器日志的例子來討論一對非常多的問題。由于每個mongodb的文檔有16M的大小限制,所以即使你是存儲ObjectID也是不夠的。我們可以使用很經(jīng)典的處理方法“父級引用”---用一個文檔存儲主機,在每個日志文檔中保存這個主機的ObjectID。
?
?
以下是個和第二中方案稍微不同的應(yīng)用級別的join用來查找一臺主機最近5000條的日志信息
?
?
所以,即使這種簡單的討論也有能察覺出mongobd的建模和關(guān)系模型建模的不同之處。你必須要注意一下兩個因素:
Will the entities on the “N” side of the One-to-N ever need to stand alone?
一對多中的多是否需要一個單獨的實體。
What is the cardinality of the relationship: is it one-to-few; one-to-many; or one-to-squillions?
這個關(guān)系中集合的規(guī)模是一對很少,很多,還是非常多。
Based on these factors, you can pick one of the three basic One-to-N schema designs:
基于以上因素來決定采取一下三種建模的方式
一對很少且不需要單獨訪問內(nèi)嵌內(nèi)容的情況下可以使用內(nèi)嵌多的一方。
一對多且多的一端內(nèi)容因為各種理由需要單獨存在的情況下可以通過數(shù)組的方式引用多的一方的。
一對非常多的情況下,請將一的那端引用嵌入進多的一端對象中。
Part 2
原文:6 Rules of Thumb for MongoDB Schema Design: Part 2
By William Zola, Lead Technical Support Engineer at MongoDB
在上一篇文章中我介紹了三種基本的設(shè)計方案:內(nèi)嵌,子引用,父引用,同時說明了在選擇方案時需要考慮的兩個關(guān)鍵因素。
一對多中的多是否需要一個單獨的實體。
這個關(guān)系中集合的規(guī)模是一對很少,很多,還是非常多。
在掌握了以上基礎(chǔ)技術(shù)后,我將會介紹更為高級的主題:雙向關(guān)聯(lián)和反范式化。
雙向關(guān)聯(lián)
如果你想讓你的設(shè)計更酷,你可以讓引用的“one”端和“many”端同時保存對方的引用。
以上一篇文章討論過的任務(wù)跟蹤系統(tǒng)為例。有person和task兩個集合,one-to-n的關(guān)系是從person端到task端。在需要獲取person所有的task這個場景下需要在person這個對象中保存有task的id數(shù)組,如下面代碼所示。
?
?
在某些場景中這個應(yīng)用需要顯示任務(wù)的列表(例如顯示一個多人協(xié)作項目中所有的任務(wù)),為了能夠快速的獲取某個用戶負責的項目可以在task對象中嵌入附加的person引用關(guān)系。
?
這個方案具有所有的一對多方案的優(yōu)缺點,但是通過添加附加的引用關(guān)系。在task文檔對象中添加額外的“owner”引用可以很快的找到某個task的所有者,但是如果想將一個task分配給其他person就需要更新引用中的person和task這兩個對象(熟悉關(guān)系數(shù)據(jù)庫的童鞋會發(fā)現(xiàn)這樣就沒法保證操作的原子性。當然,這對任務(wù)跟蹤系統(tǒng)來說并沒有什么問題,但是你必須考慮你的用例是否能夠容忍)
在一對多關(guān)系中應(yīng)用反范式
在你的設(shè)計中加入反范式,可以使你避免應(yīng)用層級別的join讀取,當然,代價是這也會讓你在更新是需要操作更多數(shù)據(jù)。下面我會舉個例子來進行說明
反范式Many -< One
以產(chǎn)品和零件為例,你可以在parts數(shù)組中冗余存儲零件的名字。以下是沒有加入反范式設(shè)計的結(jié)構(gòu)。
反范式化意味著你不需要執(zhí)行一個應(yīng)用層級別的join去顯示一個產(chǎn)品所有的零件名字,當然如果你同時還需要其他零件信息那這個應(yīng)用層的join是避免不了的。
?
在使得獲取零件名字簡單的同時,執(zhí)行一個應(yīng)用層級別的join會和之前的代碼有些區(qū)別,具體如下:
反范式化在節(jié)省你讀的代價的同時會帶來更新的代價:如果你將零件的名字冗余到產(chǎn)品的文檔對象中,那么你想更改某個零件的名字你就必須同時更新所有包含這個零件的產(chǎn)品對象。
在一個讀比寫頻率高的多的系統(tǒng)里,反范式是有使用的意義的。如果你很經(jīng)常的需要高效的讀取冗余的數(shù)據(jù),但是幾乎不去變更他d話,那么付出更新上的代價還是值得的。更新的頻率越高,這種設(shè)計方案的帶來的好處越少。
例如:假設(shè)零件的名字變化的頻率很低,但是零件的庫存變化很頻繁,那么你可以冗余零件的名字到產(chǎn)品對象中,但是別冗余零件的庫存。
需要注意的是,一旦你冗余了一個字段,那么對于這個字段的更新將不在是原子的。和上面雙向引用的例子一樣,如果你在零件對象中更新了零件的名字,那么更新產(chǎn)品對象中保存的名字字段前將會存在短時間的不一致。
反范式One -< Many
你也可以冗余one端的數(shù)據(jù)到many端:
如果你冗余產(chǎn)品的名字到零件表中,那么一旦更新產(chǎn)品的名字就必須更新所有和這個產(chǎn)品有關(guān)的零件,這比起只更新一個產(chǎn)品對象來說代價明顯更大。這種情況下,更應(yīng)該慎重的考慮讀寫頻率。
在一對很多的關(guān)系中應(yīng)用反范式
在日志系統(tǒng)這個一對許多的例子中也可以應(yīng)用反范式化的技術(shù)。你可以將one端(主機對象)冗余到日志對象中,或者反之。
下面的例子將主機中的IP地址冗余到日志對象中。
?
如果想獲取最近某個ip地址的日志信息就變的很簡單,只需要一條語句而不是之前的兩條就能完成。
事實上,如果one端只有少量的信息存儲,你甚至可以全部冗余存儲到多端上,合并兩個對象。
另一方面,也可以冗余數(shù)據(jù)到one端。比如說你想在主機文檔中保存最近的1000條日志,可以使用mongodb 2.4中新加入的$eache/$slice功能來保證list有序而且只保存1000條。
日志對象保存在logmsg集合中,同時冗余到hosts對象中。這樣即使hosts對象中超過1000條的數(shù)據(jù)也不會導(dǎo)致日志對象丟失。
?
通過在查詢中使用投影參數(shù) (類似{_id:1})的方式在不需要使用logmsgs數(shù)組的情況下避免獲取整個mongodb對象,1000個日志信息帶來的網(wǎng)絡(luò)開銷是很大的。
在一對多的情況下,需要慎重的考慮讀和更新的頻率。冗余日志信息到主機文檔對象中只有在日志對象幾乎不會發(fā)生更新的情況下才是個好的決定。
總結(jié)
在這篇文章里,我介紹了對三種基礎(chǔ)方案:內(nèi)嵌文檔,子引用,父引用的補充選擇。
使用雙向引用來優(yōu)化你的數(shù)據(jù)庫架構(gòu),前提是你能接受無法原子更新的代價。
可以在引用關(guān)系中冗余數(shù)據(jù)到one端或者N端。
在決定是否采用反范式化時需要考慮下面的因素:
你將無法對冗余的數(shù)據(jù)進行原子更新。
只有讀寫比較高的情況下才應(yīng)該采取反范式化的設(shè)計。
Part 3
原文:6 Rules of Thumb for MongoDB Schema Design: Part 3
By William Zola, Lead Technical Support Engineer at MongoDB
這篇文章是系列的最后一篇。在第一篇文章里,我介紹了三種針對“一對多?”關(guān)系建模的基礎(chǔ)方案。在第二篇文章中,我介紹了對基礎(chǔ)方案的擴展:雙向關(guān)聯(lián)和反范式化。
反范式可以讓你避免一些應(yīng)用層級別的join,但是這也會讓更新變的更復(fù)雜,開銷更大。不過冗余那些讀取頻率遠遠大于更新頻率的字段還是值得的。
如果你還沒有讀過前兩篇文章,歡迎一覽。
讓我們回顧下這些方案
你可以采取內(nèi)嵌,或者建立one端或者N端的引用,也可以三者兼而有之。
你可以在one端或者N端冗余多個字段
下面這些是你需要謹記的:
1、優(yōu)先考慮內(nèi)嵌,除非有什么迫不得已的原因。
2、需要單獨訪問一個對象,那這個對象就不適合被內(nèi)嵌到其他對象中。
3、數(shù)組不應(yīng)該無限制增長。如果many端有數(shù)百個文檔對象就不要去內(nèi)嵌他們可以采用引用ObjectID的方案;如果有數(shù)千個文檔對象,那么就不要內(nèi)嵌ObjectID的數(shù)組。該采取哪些方案取決于數(shù)組的大小。
4、不要害怕應(yīng)用層級別的join:如果索引建的正確并且通過投影條件(第二章提及)限制返回的結(jié)果,那么應(yīng)用層級別的join并不會比關(guān)系數(shù)據(jù)庫中join開銷大多少。
5、在進行反范式設(shè)計時請先確認讀寫比。一個幾乎不更改只是讀取的字段才適合冗余到其他對象中。
6、在mongodb中如何對你的數(shù)據(jù)建模,取決于你的應(yīng)用程序如何去訪問它們。數(shù)據(jù)的結(jié)構(gòu)要去適應(yīng)你的程序的讀寫場景。
設(shè)計指南
當你在MongoDB中對“一對多”關(guān)系進行建模,你有很多的方案可供選擇,所以你必須很謹慎的去考慮數(shù)據(jù)的結(jié)構(gòu)。下面這些問題是你必須認真思考的:
關(guān)系中集合的規(guī)模有多大:是一對很少,很多,還是非常多?
對于一對多中”多“的那一端,是否需要單獨的訪問它們,還是說它們只會在父對象的上下文中被訪問。
被冗余的字段的讀寫的比例是多少?
數(shù)據(jù)建模設(shè)計指南
在一對很少的情況下,你可以在父文檔中內(nèi)嵌數(shù)組。
在一對很多或者需要單獨訪問“N”端的數(shù)據(jù)時,你可以采用數(shù)組引用ObjectID的方式。如果可以加速你的訪問也可以在“N”端使用父引用。
在一對非常多的情況下,可以在“N”端使用父引用。
如果你打算在你的設(shè)計中引入冗余等反范式設(shè)計,那么你必須確保那些冗余的數(shù)據(jù)讀取的頻率遠遠大于更新的頻率。而且你也不需要很強的一致性。因為反范式化的設(shè)計會讓你在更新冗余字段時付出一定的代價(更慢,非原子化)
轉(zhuǎn)載于:https://www.cnblogs.com/WeiGe/p/4903850.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的MongoDB数据库设计中6条重要的经验法则的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 84岁济公演戏 济公扮演者游本昌近况个人
- 下一篇: [redis] 分布式 Redis 的