MongoDB数据建模介绍
MongoDB數據建模介紹
數據建模需要在滿足應用需求、數據庫引擎的性能特征、以及數據檢索模式之間取得平衡。在設計數據模型時,請始終考慮應用程序對數據的使用場景(比如,查詢,更新,和數據處理)以及數據本身的結構。
靈活的模型
不同于SQL數據庫,插入數據前必須聲明表的模型。默認情況下,MongoDB的集合不要求其文檔具有相同的模型:
- 單個集合內的文檔不必擁有相同的字段,并且字段的數據類型也可以不同。
- 如果要更改集合內的文檔結構(比如增加新字段,去除字段,或修改字段的值),直接更改即可。
這種靈活性有助于將文檔映射到實體或對象上。集合內的每個文檔都可以單獨匹配其表示實體的數據字段。
不過在實際應用中,集合內的文檔通常具有類似的結構,并且你也可以在執行更新和插入操作時,使用集合的文檔驗證規則。具體查看:https://docs.mongodb.com/manual/core/schema-validation/
文檔結構
為MongoDB應用設計數據模型時,關鍵是文檔結構以及應用程序如何表示數據之間的關系。MongoDB允許在單個文檔中嵌入關聯數據。
子文檔
子文檔可以存儲關聯數據,并嵌在文檔的某個字段或者數組中。這些反范式的數據模型可以方便應用程序在單次數據庫操作中檢索和操作相關數據。在很多場景下,這種反范式的數據模型都是非常理想的。
// a user document {_id: <ObjectId1>,username: 'ayhan',gender: 'male',age: 18,extra: {email: 'abc@example.com',school: 'xxx univeirsity',hobby: 'reading',} }嵌入式數據模型允許應用在一條記錄中存儲關聯的信息片段。這樣的好處是,應用在完成常見的操作時,可以發出更少的查詢和更新。
通常在以下情況下使用嵌入式數據模型:
- 實體之間存在“包含”關系,比如一對一。
- 實體之間存在“一對多”關系,在這種關系中,“多”的文檔常常在“一”的文檔的上下文中出現或被查看(反之亦然)。
通常,嵌入式能為讀操作提供更好的性能,以及在單次數據庫操作中請求和檢索關聯數據的能力。這也使得在單個原子性寫操作中更新關聯的數據成為可能。要訪問嵌入的子文檔,使用.操作即可。
包含子文檔的文檔大小也不得超過BSON文檔尺寸的最大值,也就是16MB。
引用
通過文檔間的鏈接(或引用),引用用來存儲著數據間的關系。個人理解,類似于SQL中的外鍵。應用可以解析這些引用來訪問相關的數據。廣義上來講,這些是范式化的數據模型。
// a user document {_id: <ObjectId1>,username: 'ayhan',gender: 'male',age: 18 }// a extra document {_id: <ObjectId2>,email: 'abc@example.com',school: 'xxx univeirsity',hobby: 'reading',user_id: <ObjectId1> // 通過user的主鍵,引用user }通常在以下情況下使用范式化的數據模型:
- 嵌入式會導致數據冗余,但又不能提供足夠的性能優勢時
- 描述更復雜的多對多的關系時
- 模擬大型的分層數據集時(樹形結構)
引用比起嵌入式能提供更大的靈活性。但是客戶端應用必須發起后續查詢來解析引用,也就是說,范式化的數據模型需要更多的查詢請求。
寫操作的原子性
單文檔原子性
在MongoDB中,寫操作在單個文檔上保證原子性,即使這個操作修改了單個文檔的多個子文檔。如果單次寫操作修改了多個文檔(比如,db.collection.update_many()),那么對每個文檔的修改是原子性的,但是這整個操作并不是原子性的。支持在單個文檔中嵌入子文檔的這種反范式化數據結構,比起跨多個文檔和集合的范式化方式,更有助于原子性操作。
從4.0版本開始,對于需要多文檔讀寫保證原子性的場景,MongoDB為副本集提供了多文檔事務支持。具體查看:https://docs.mongodb.com/manual/core/transactions/,
多文檔事務
注意:
在多數情況下,多文檔事務會導致更多的性能開銷。盡管有多文檔事務支持,但也不應該以此來取代高效的模型設計。對于很多場景,反范式化的數據模型(子文檔和數組)仍然是最好的選擇。也就是說,合適的數據建模將最大限度地減少對多文檔事務的需求。
數據的使用和性能
在設計數據模型時,請考慮應用程序如何使用你的數據庫。例如,如果你的應用只使用最近插入的文檔,請考慮使用Capped Collections。或者你的應用主要是對集合的讀操作,那么對常見的查詢添加索引有助于提高性能。
更多信息可查看:操作因素和數據模型
分片
MongoDB使用分片提供水平伸縮。這些集群支持可以支持大型數據集和超高的吞吐量的操作。分片允許用戶對數據庫內的集合做分區,然后在多個實例或分片間分發文檔。
MongoDB使用分片鍵(shard key)在分片集合中分發數據和應用程序流量。選擇合適的分片鍵對性能影響重大,并且可以啟用或阻止查詢隔離并增加寫入容量。仔細考慮要用作分片鍵的字段非常重要。
見分片和 分片鍵以獲取更多信息。
索引
使用索引可以提高常見查詢的性能。請對查詢中經常出現的字段創建索引,為返回排序結果的所有操作創建索引。MongoDB會自動為_id字段創建索引。創建索引時,需要考慮一下索引行為:
- 每個索引至少需要8kb的數據空間
- 增加索引會對寫操作產生負面的性能影響。對于具有高寫讀比的集合,索引是昂貴的開銷,因為每次插入都需要更新任何索引
- 具有高讀寫比的集合通常會從索引中受益
- 處于活動狀態時,每個索引都會占據磁盤空間和內存。這種用量可能會非常重要,并且出于容量規劃,也應該被監控起來,尤其要關注工作集的大小。
有關索引以及分析查詢性能的詳細信息,請參閱索引策略。此外,MongoDB 數據庫分析器(database profiler)可以幫助定位低效的查詢。
大量的集合
在某些情況下,你可以選擇將信息存儲在多個不同的集合中,而不是單個集合。通常情況下,大量的集合并不會導致性能損失,并且可能對性能很有幫助。對于高吞吐量的批處理,不同的集合非常重要。在使用具有大量集合的模型時,請考慮以下行為:
-
每個集合都有幾千字節的最小開銷
-
每個索引(包括_id索引),都需要至少8kb的數據空間
-
每個數據庫都有一個命名空間文件(namespace file,比如.ns),用來存儲該數據庫所有的元數據,并且每個索引和集合在命名空間中有自己的條目。MongoDB對命名空間的大小有限制(更多參考)
-
使用mmapv1存儲引擎的MongoDB 對命名空間的數量有限制。如果你想知道當前當前命名空間的數量,可以在MongoDB shell中運行如下命令:
db.systems.namespaces.count()命名空間數量的限制取決于<database>.ns 大小。命名空間文件默認為16 MB。如果要更改新命名空間的大小,可以在啟動服務增加選項:--nssize <new size MB>. 對于現有的數據庫,以--nssize選項啟動后,在MongoDB shell執行db.repairDatabase()命令。
集合包含大量的小文檔
出于性能原因,如果您的集合包含大量小文檔,則應考慮嵌入。如果您可以通過某種邏輯關系對這些小文檔進行分組,并且經常通過此分組檢索這些文檔,則可以考慮將小文檔“匯總”為包含嵌入文檔數組的較大文檔。
將這些小文檔“匯總”為邏輯分組意味著在檢索一組文檔時,查詢是順序讀取的,且只需較少的隨機磁盤訪問。此外,“匯總”文檔并將公共字段移動到較大的文檔有益于這些字段的索引。只需要更少的公共字段的副本,索引時更少的相關鍵條目。有關索引的更多信息,請參閱 索引。
但是,如果您通常只需要檢索分組內文檔的子集,那么“匯總”文檔可能無法提供更好的性能。此外,如果小的單獨文檔代表數據的自然模型,則應該維護該模型。
小文檔的存儲優化
每個MongoDB文檔都包含一定的開銷。這種開銷通常是無關緊要的,但如果所有文檔都只是幾個字節,比如集合中的文檔只有一個或兩個字段的情況下,則另當別論。
請考慮以下建議和策略,以優化這些集合的存儲利用率:
-
_id明確使用該字段。
MongoDB客戶端自動為每個文檔添加一個_id字段,并為該 字段生成唯一的12位字節的ObjectId。此外,MongoDB總是索引_id`字段。對于較小的文檔,這可能占用大量空間。
為了優化存儲使用,用戶可以在插入文檔時顯式指定該字段的值,只要能滿足主鍵約束即可。
-
使用較短的字段名稱。
注意
縮短字段名稱不僅降低表達能力,也不會為較大的文檔提供的明顯的好處,因為對于較大的文檔,文檔開銷并沒那么重要。此外,較短的字段名稱不會減小索引的大小,因為索引具有預定義的結構。
通常,沒有必要使用短字段名稱。
MongoDB將所有字段名稱存儲在每個文檔中。對于大多數文檔,這只占文檔使用空間的一小部分;但是,對于小型文檔,這可能占用更多的空間比例。考慮一組類似于以下內容的小文檔:
{ last_name : “Smith” , best_score : 3.9 }如果對字段名進行如下縮短,那么每個文檔可以節約9個字節。
{ lname : “Smith” , 得分 : 3.9 } -
嵌入文檔。
在某些情況下,您可能希望將文檔嵌入到其他文檔中,并節省每個文檔的開銷。請參見 集合包含大量小文檔。
數據生命周期管理
數據建模時應考慮數據的生命周期管理。
有生存時間或TTL特性的集合在一段時間后,其文檔會過期。如果您的應用需要某些數據在數據庫中保留一段有限的時間,請考慮使用TTL特性。
此外,如果您的應用僅使用最近插入的文檔,請考慮上限集合(Capped Collections),可對插入的文檔進行先入先出的管理,有效地支持插入和讀取基于插入順序的文檔操作。
文檔的增長和MMAPv1
某些更新,比如往數組插入元素,或者增加字段,會增加文件的大小。
對于已經棄用的MMAPv1文檔引擎,如果文檔大小超出了該文檔的分配空間,MongoDB會在磁盤上重新分配該文檔。使用廢棄的MMAPv1引擎時,出于對文檔增長的考慮,將會影響對范式化化或反范式化數據的決策。
在已創建的文檔中嵌入數據會導致文檔的增長。使用已棄用的MMAPv1存儲引擎,文檔增長會影響寫入性能并導致數據碎片化。從版本3.0.0開始,MongoDB使用Power of 2 Sized Allocations取代MMAPv1的默認分配策略,以最大限度地減少數據碎片的可能性。有關詳細信息,請參閱 Power of 2 Sized Allocations 。
其他參考資源
- Thinking in Documents Part 1 (Blog Post)
數據模型示例和模式
文檔間的模型關系
基于子文檔的一對一
使用子文檔來描述關聯數據間的一對一關系。下面以顧客和地址為例子,每個顧客對應一個地址。
如果用范式化的數據模型,address文檔包含對customer文檔的引用:
// customer 文檔 {_id: "joe",name: "Joe Bookreader" }// address 文檔 {customer_id: "joe", street: "123 Fake Street",city: "Faketon",state: "MA",zip: "12345" }如果要經常通過customer信息來獲取address信息,在引用的情況下,你的應用需要發出多個查詢來解析引用。但是如果使用子文檔,應用通過一次查詢就可以檢索到所有信息:
{_id: "joe",name: "Joe Bookreader",address: { // address 作為子文檔street: "123 Fake Street",city: "Faketon",state: "MA",zip: "12345"} }這個示例說明了,如果要在一個數據實體的上下文中查看另一個數據實體,子文檔對于引用的優勢。
基于子文檔的一對多
使用子文檔來描述關聯數據間的一對多關系。下面以顧客和地址為例子,每個顧客對應多個地址。
使用范式化數據模型,多個address文檔包含對customer文檔的引用:
{_id: "joe",name: "Joe Bookreader" }{customer_id: "joe",street: "123 Fake Street",city: "Faketon",state: "MA",zip: "12345" }{customer_id: "joe",street: "1 Some Other Street",city: "Boston",state: "MA",zip: "12345" }如果要經常通過customer信息來獲取其address信息,在引用的情況下,你的應用需要發出多個查詢來解析引用。更好的選擇是把address數據實體嵌入customer的數組字段中,這樣應用通過一次查詢就可以檢索到所有信息:
{_id: "joe",name: "Joe Bookreader",addresses: [{street: "123 Fake Street",city: "Faketon",state: "MA",zip: "12345"},{street: "1 Some Other Street",city: "Boston",state: "MA",zip: "12345"}]}適用情況
子文檔的一對多,這里的多通常只有幾個或者幾十個,一般不超過3位數,是比較合適的。如果特別多,考慮使用接下來的引用。
基于引用的一對多
使用文檔間的引用來描述關聯數據間的一對多關系。下面以出版社和書為例子,每個出版社可以對應多本書。
如果使用子文檔,將publisher文檔嵌入book文檔中,會導致publisher信息重復:
{title: "MongoDB: The Definitive Guide",author: [ "Kristina Chodorow", "Mike Dirolf" ],published_date: ISODate("2010-09-24"),pages: 216,language: "English",publisher: { // publishername: "O'Reilly Media",founded: 1980,location: "CA"} }{title: "50 Tips and Tricks for MongoDB Developer",author: "Kristina Chodorow",published_date: ISODate("2011-05-06"),pages: 68,language: "English",publisher: { // publisher 重復name: "O'Reilly Media",founded: 1980,location: "CA"}}為了避免這種重復,可以使用引用,將publisher的信息單獨保存到一個集合中。
使用引用時,關系的增長決定在哪邊存儲引用關系。如果每個publisher發布的book很少且有限,那么在publisher中包含對book的引用是合適的。但是,如果publisher發布的book數量巨大且沒有限制,這樣的數據模型將導致不斷地變化,數組不斷增長,就像下面這樣:
{name: "O'Reilly Media",founded: 1980,location: "CA",books: [123456789, 234567890, ...] // 通過數組字段,存儲對book的引用,但是數組將不斷變大}{_id: 123456789,title: "MongoDB: The Definitive Guide",author: [ "Kristina Chodorow", "Mike Dirolf" ],published_date: ISODate("2010-09-24"),pages: 216,language: "English" }{_id: 234567890,title: "50 Tips and Tricks for MongoDB Developer",author: "Kristina Chodorow",published_date: ISODate("2011-05-06"),pages: 68,language: "English" }為了避免上述這種情況,應當在book中包含對publisher的引用:
{_id: "oreilly",name: "O'Reilly Media",founded: 1980,location: "CA" }{_id: 123456789,title: "MongoDB: The Definitive Guide",author: [ "Kristina Chodorow", "Mike Dirolf" ],published_date: ISODate("2010-09-24"),pages: 216,language: "English",publisher_id: "oreilly" // book中引用publisher}{_id: 234567890,title: "50 Tips and Tricks for MongoDB Developer",author: "Kristina Chodorow",published_date: ISODate("2011-05-06"),pages: 68,language: "English",publisher_id: "oreilly" // book中引用publisher}適用情況
通常來說,如果書不會超過數千本,將其書的實體放在出版社的數組中引用是合適的。但是如果更多甚至沒有上限,那么應該考慮將出版社放在書的實體中引用。對于前者,當然也可以同時在書的實體中包含對出版社的引用,這種雙向關聯可以提供靈活的檢索。
利用冗余提高性能
另外我們也可以結合使用范式化化和反范式化模型,比如:
{name: "O'Reilly Media",founded: 1980,location: "CA",books: [{id: 123456789, title: '50 Tips and Tricks for MongoDB Developer'} // 存儲對書籍的引用,額外冗余書籍的標題{id: 234567890, title: '50 Tips and Tricks for MongoDB Developer'}]}這樣,如果要顯示某個publisher出版的所有書名,就需不要額外的查詢或者連表操作了。
結合使用反范式化模型雖然減少了讀的代價,但是將book的title冗余到publisher中,如果想修改book的title,那么需要同時修改兩處地方(非原子性操作)。因此,如果讀寫比特別高,經常需要讀取冗余的數據,但幾乎不需要變更數據的情況下,這么做是值得的。反之,對數據更新的需求越高,這種方案帶來的好處越少。建議是,可以冗余關聯實體中不變的字段(或讀取頻率遠遠大于更新頻率的字段)。比如,可以冗余書籍的title,但別冗余書籍的庫存。
結合使用反范式模型,也可以反過來應用,比如在直接在book實體中冗余publisher的name字段:
{_id: 234567890,title: "50 Tips and Tricks for MongoDB Developer",author: "Kristina Chodorow",published_date: ISODate("2011-05-06"),pages: 68,language: "English",publisher_id: "oreilly", // book中引用publisherpublisher_name: "O'Reilly Media" // 冗余publisher的name字段}最后,也不要陷入極端,害怕在應用層使用聯結查詢:
如果建立正確的索引,并使用投影限制返回的字段,額外查詢的開銷也是可以接受的,并不會比關系型數據庫的join操作開銷大多少。
樹結構模式
父節點引用模式
通過在子節點中存儲對父節點的引用,來描述一種樹狀結構。如下圖:
下面通過parent字段,引用父節點的_id,對以上數結構建模:
db.categories.insert( { _id: "MongoDB", parent: "Databases" } ) db.categories.insert( { _id: "dbm", parent: "Databases" } ) db.categories.insert( { _id: "Databases", parent: "Programming" } ) db.categories.insert( { _id: "Languages", parent: "Programming" } ) db.categories.insert( { _id: "Programming", parent: "Books" } ) db.categories.insert( { _id: "Books", parent: null } )- 檢索某個節點的的父節點:
- 你可以在parent字段上創建索引,以提高父節點的搜索速度:
- 查找Databases節點的所有直接子節點:
- 如果要檢索子樹,請參閱$graphLookup
子節點引用模式
通過在父節點中存儲對子節點的引用,也能描述同樣的樹狀結構:
下面通過children數組字段,引用子節點的_id,對以上樹結構建模:
db.categories.insert( { _id: "MongoDB", children: [] } ) db.categories.insert( { _id: "dbm", children: [] } ) db.categories.insert( { _id: "Databases", children: [ "MongoDB", "dbm" ] } ) db.categories.insert( { _id: "Languages", children: [] } ) db.categories.insert( { _id: "Programming", children: [ "Databases", "Languages" ] } ) db.categories.insert( { _id: "Books", children: [ "Programming" ] } )- 檢索某個節點的直接子節點
- 在children字段上建索引,以提高子節點的搜索速度
- 查找MongoDB節點的所有父節點及其兄弟節點
只要不需要對子樹進行操作,子節點引用模式為樹型存儲提供了一個合適方案。該模式也適用于一個節點有多個父節點的情況(上圖倒過來)。
祖先數組模式
通過在子節點中引用父節點,以及祖先數組(太爺爺,爺爺,父節點,…),來描述樹型結構:
下面通過祖先數組,進行樹型結構建模。除了ancestors數組字段,文檔也通過parent字段存儲對對直接父節點的引用。
db.categories.insert( { _id: "MongoDB", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } ) db.categories.insert( { _id: "dbm", ancestors: [ "Books", "Programming", "Databases" ], parent: "Databases" } ) db.categories.insert( { _id: "Databases", ancestors: [ "Books", "Programming" ], parent: "Programming" } ) db.categories.insert( { _id: "Languages", ancestors: [ "Books", "Programming" ], parent: "Programming" } ) db.categories.insert( { _id: "Programming", ancestors: [ "Books" ], parent: "Books" } ) db.categories.insert( { _id: "Books", ancestors: [ ], parent: null } )-
查詢某個節點的祖先
db.categories.findOne({_id: "MongoDB"}).ancestors -
為ancestors字段創建索引,以加快祖先節點的查詢速度
db.categories.createIndex({ancestors: 1}) -
查詢某個節點的所有后代節點
db.categories.find({ancestors: "Programming"})
祖先數組模式通過為祖先字段創建索引,為查找某個節點的后代和祖先提供了一種快速高效的解決方案。這使得該模式成為使用子樹的不錯選擇。
祖先數組模式比接下來要講的物化路徑模式稍慢,但是更易用。
物化路徑模式
通過存儲文檔間完整的關系路徑,來描述樹型結構:
下面通過在path字段中存儲路徑的字符串,來進行樹型結構建模,路徑之間以,逗號作為分隔符:
db.categories.insert( { _id: "Books", path: null } ) db.categories.insert( { _id: "Programming", path: ",Books," } ) db.categories.insert( { _id: "Databases", path: ",Books,Programming," } ) db.categories.insert( { _id: "Languages", path: ",Books,Programming," } ) db.categories.insert( { _id: "MongoDB", path: ",Books,Programming,Databases," } ) db.categories.insert( { _id: "dbm", path: ",Books,Programming,Databases," } )-
檢索整個樹,并按path字段排序:
db.categories.find().sort({path: 1}) -
對path字段使用正則來查找某個節點的所有后代:
db.categories.find({path: /,Programming,/}) -
你也可以檢索最高層Books的所有后代:
db.categories.find{path: /^,Books,/} -
為path字段創建索引:
db.categories.createIndex({path: 1})這個索引可能提高某些查詢的性能:
- 從根路徑開始的子樹查詢將獲得顯著性能提升,比如:/^,Books,/或者/^,Books,Programming,/
- 未從根路徑開始的子樹查詢,如/,Programming,/,將檢查整個索引。這種情況下,如果索引顯著小于整個集合,也會獲得一些性能提升。
嵌套集模式
該模式適合靜態的樹結構,暫不討論。
特定應用模式
原子操作
如之前所述,在MongoDB中,寫操作在單個文檔上保證原子性。即便MongoDB從4.0開始支持多事務,但在很多情況下,采用反范式的數據模型(子文檔),仍然是最佳選擇。因此對于那些必須一起更新的字段,可以考慮將其嵌入一個文檔中,以保證原子性操作。
比如,書籍的庫存和結賬信息應該是同步的,因此在同一文檔中嵌入available和checkout字段可以確保對這兩個字段的原子性更新:
{_id: 123456789,title: "MongoDB: The Definitive Guide",author: [ "Kristina Chodorow", "Mike Dirolf" ],published_date: ISODate("2010-09-24"),pages: 216,language: "English",publisher_id: "oreilly",available: 3,checkout: [ { by: "joe", date: ISODate("2012-10-15") } ] }如果有新的結賬信息要更新,像下面這樣就可以了:
db.books.updateOne({{_id: 123456789, available: {$gt: 0}},{$inc: {available: -1},$push: {checkout: {by: "abc", date: new Date()}}} })關鍵字搜索
如果你的應用需要對包含文本內容的字段進行查詢,你可以進行完全匹配,或者使用正則進行匹配。然后,這種方式在很多時候無法滿足要求。
下面提供一種支持關鍵字搜索的方法:將要用的關鍵字存儲到文檔的一個數組字段中,并創建為該字段創建多鍵索引。
比如,搜索書籍時:
{ title : "Moby-Dick" ,author : "Herman Melville" ,published : 1851 ,ISBN : 0451526996 ,topics : [ "whaling" , "allegory" , "revenge" , "American" , // 將要搜索的關鍵詞放在topic數組中"novel" , "nautical" , "voyage" , "Cape Cod" ] }對topic數組字段創建多鍵索引:
db.books.createIndex({topics: 1})多鍵索引將會為數組中的每個關鍵字創建單獨的索引條目,然后你就可以依據關鍵字進行查詢:
db.books.findOne({topic: "voyage"}, {title: 1}) // 依據voyage關鍵字查詢書籍,并只返回title字段如果數組中包含大量關鍵字,比如成百上千個,將導致插入時更大的索引開銷。
關鍵字索引的局限
比起全文產品,關鍵字索引在以下方面存在不足或者不適用:
- 截取。不能解析根或相關單詞關鍵字。
- 同義詞。只能由應用層為同義詞或相關查詢提供支持。
- 排名。不提供加權結果的方法。
- 異步索引。MongoDB同步構建索引,這意味著索引始終是最新的,然后異步批量索引有些時候可能更高效。
關鍵字索引的
貨幣數據
暫不討論
時間數據
MongoDB默認以UTC格式存儲時間,并將任何本地時間表示轉化為這種形式。時區信息由應用程序保存,并據此計算本地時間。
例如,你可以將當前時間,以及當前客戶端與UTC的偏移量都存下來:
var now = new Date(); var offset = now.getTimezoneOffset(); // -480, 比UTC快480分鐘 db.data.save({date: now,offset: offset });重建本地原始時間:
var record = db.data.findOne(); var localNow = new Date(record.date.getTime() - (record.offset * 60000))數據建模引用
通常來說,采用反范式的數據模型(子文檔)是最佳選擇。但是,在某些情況下,將關聯的信息分開存儲到不同的集合或者數據庫中也是合理的。
注意:MongoDB 3.2 引入了$lookup管道,以便對同一數據庫內的集合進行左外連接操作(主動連表)。更多信息和示例,請參閱$lookup
從3.4開始,還可以使用$graphLookup管道連接集合執行遞歸搜索。更多信息和示例,請參閱 $graphLookup。
在$lookup和$graphLookup出現前,你可以采用本部分所介紹的方案:
手動引用(Manual references)
在一個文檔中保存另一個文檔的_id字段作為引用。然后應用程序在需要時執行第二次查詢來獲取關聯數據。大多數情況下,手動引用簡單且夠用。
但是,手動引用無法傳達數據庫和集合的名字,所以,如果一個集合內的文檔與多個其它集合的文檔關聯,你可能需要考慮使用DBRefs。
DBRefs
一個文檔通過另一個文檔的_id字段,集合名,以及(可選的)數據庫名進行引用。通過包含這些名稱,DBRefs使得跨集合的文檔鏈接更容易。
如果要解析DBRefs,應用程序必須執行額外的查詢已獲得被引用的文檔。很多驅動提供了輔助方法來自動執行DBRef查詢,但不會自動將DBRefs解析為文檔。
下面我們看看如何進行DBRef引用:
// users集合 {"_id": 12345678 }// post集合 {"_id": 87654321,"title": "a beautiful poem","body": "asdif asd fiasdf jasdlf idf jdjifj idfj idlllle iidjfjjfi asssdf","author": {"$ref": "users", // 被引用文檔所在的集合名"$id": "12345678", // 被引用文檔的_id值"$db": "web-application" // (可選)被引用文檔所在的數據庫} }DBRef格式如下:
{ "$ref" : <value>, "$id" : <value>, "$db" : <value> } // 注意,必須按照這個字段順序最后,除非你有令人信服的理由使用DBRefs,否則請選擇手動引用。
總結
以上是生活随笔為你收集整理的MongoDB数据建模介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: zoj 3762(求三角形的最大高)
- 下一篇: EL表达式隐含对象和jstl命名冲突,j