讓我們換換思維,對 MongoDB 進行一個更抽象的理解。介紹一些新的術語和一些新的語法是非常容易的。而要接受一個以新的范式來建模,是相當不簡單的。事實是,當用新技術進行建模的時候,我們中的許多人還在找什么可用的什么不可用。在這里我們只是開始新的開端,而最終你需要去在實戰中練習和學習。
與大多數 NoSQL 數據庫相比,面向文檔型數據庫和關系型數據庫很相似 - 至少,在建模上是這樣的。但是,不同點非常重要。
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#no-joins)No Joins
你需要適應的第一個,也是最根本的區別就是 mongoDB 沒有鏈接(join) 。我不知道 MongoDB 中不支持鏈接的具體原因,但是我知道鏈接基本上意味著不可擴展。就是說,一旦你把數據水平擴展,無論如何你都要放棄在客戶端(應用服務器)使用鏈接。事實就是,數據?_有_?關系, 但 MongoDB 不支持鏈接。
沒別的辦法,為了在無連接的世界生存下去,我們只能在我們的應用代碼中自己實現鏈接。我們需要進行二次查詢?`find`,把相關數據保存到另一個集合中。我們設置數據和在關系型數據中聲明一個外鍵沒什么區別。先不管我們那美麗的`unicorns`?了,讓我們來看看我們的?`employees`。 首先我們來創建一個雇主 (我提供了一個明確的?`_id`?,這樣我們就可以和例子作成一樣)
~~~
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d730"),
name: 'Leto'})
~~~
然后讓我們加幾個工人,把他們的管理者設置為?`Leto`:
~~~
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d731"),
name: 'Duncan',
manager: ObjectId(
"4d85c7039ab0fd70a117d730")});
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d732"),
name: 'Moneo',
manager: ObjectId(
"4d85c7039ab0fd70a117d730")});
~~~
(有必要再重復一次,?`_id`?可以是任何形式的唯一值。因為你很可能在實際中使用?`ObjectId`?,我們也在這里用它。)
當然,要找出 Leto 的所有工人,只需要執行:
~~~
db.employees.find({manager: ObjectId(
"4d85c7039ab0fd70a117d730")})
~~~
這沒什么神奇的。在最壞的情況下,大多數的時間,為彌補無鏈接所做的僅僅是增加一個額外的查詢(可能是被索引的)。
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#數組和內嵌文檔)數組和內嵌文檔
MongoDB 不支持鏈接不意味著它沒優勢。還記得我們說過 MongoDB 支持數組作為文檔中的基本對象嗎?這在處理多對一(many-to-one)或者多對多(many-to-many)的關系的時候非常方便。舉個簡單的例子,如果一個工人有兩個管理者,我們只需要像這樣存一下數組:
~~~
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d733"),
name: 'Siona',
manager: [ObjectId(
"4d85c7039ab0fd70a117d730"),
ObjectId(
"4d85c7039ab0fd70a117d732")] })
~~~
有趣的是,對于某些文檔,`manager`?可以是單個不同的值,而另外一些可以是數組。而我們原來的?`find`?查詢依舊可用:
~~~
db.employees.find({manager: ObjectId(
"4d85c7039ab0fd70a117d730")})
~~~
你會很快就發現,數組中的值比多對多鏈接表(many-to-many join-tables)要容易處理得多。
數組之外,MongoDB 還支持內嵌文檔。來試試看向文檔插入一個內嵌文檔,像這樣:
~~~
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d734"),
name: 'Ghanima',
family: {mother: 'Chani',
father: 'Paul',
brother: ObjectId(
"4d85c7039ab0fd70a117d730")}})
~~~
像你猜的那樣,內嵌文檔可以用 dot-notation 查詢:
~~~
db.employees.find({
'family.mother': 'Chani'})
~~~
我們只簡單的介紹一下內嵌文檔適用情況,以及你怎么使用它們。
結合兩個概念,我們甚至可以內嵌文檔數組:
~~~
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d735"),
name: 'Chani',
family: [ {relation:'mother',name: 'Chani'},
{relation:'father',name: 'Paul'},
{relation:'brother', name: 'Duncan'}]})
~~~
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#反規范化denormalization)反規范化(Denormalization)
另外一個代替鏈接的方案是對你的數據做反規范化處理(denormalization)。從歷史角度看,反規范化處理是為了解決那些對性能敏感的問題,或是需要做快照的數據(比如說審計日志)。但是,隨著日益增長的普及的 NoSQL,對鏈接的支持的日益喪失,反規范化作為規范化建模的一部分變得越來越普遍了。這不意味著,應該對你文檔里的每條數據都做冗余處理。而是說,與其對冗余數據心存恐懼,讓它影響你的設計決策,不如在建模的時候考慮什么信息應當屬于什么文檔。
比如說,假設你要寫一個論壇應用。傳統的方式是通過?`posts`?中的?`userid`?列,來關聯一個特定的?`user`?和一篇?`post`。這樣的建模,你沒法在顯示?`posts`?的時候不查詢 (鏈接到)?`users`。一個代替案是簡單的在每篇?`post`?中把?`name`?和`userid`?一起保存。你可能要用到內嵌文檔,比如?`user: {id: ObjectId('Something'), name: 'Leto'}`。是的,如果你讓用戶可以更新他們的名字,那么你得對所有的文檔都進行更新(一個多重更新)。
適應這種方法不是對任何人都那么簡單的。很多情況下這樣做甚至是無意義的。不過不要害怕去嘗試。它只是在某些情況下不適用而已,但在某些情況下是最好的解決方法。
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#你的選擇是)你的選擇是?
在處理一對多(one-to-many)或者多對多(many-to-many)場景的時候,id 數組通常是一個正確的選擇。但通常,新人開發者在面對內嵌文檔和 "手工" 引用時,左右為難。
首先,你應該知道的是,一個獨立文檔的大小當前被限制在 16MB 。知道了文檔的大小限制,挺寬裕的,對你考慮怎么用它多少有些影響。在這點上,看起來大多數開發者都愿意手工維護數據引用關系。內嵌文檔經常被用到,大多數情況下多是很小的數據塊,那些總是被和父節點一起拉取的數據塊。現實的例子是為每個用戶保存一個?`addresses`?,看起來像這樣:
~~~
db.users.insert({name: 'leto',
email: 'leto@dune.gov',
addresses: [{street: "229 W. 43rd St",
city: "New York", state:"NY",zip:"10036"},
{street: "555 University",
city: "Palo Alto", state:"CA",zip:"94107"}]})
~~~
這并不意味著你要低估內嵌文檔的能力,或者僅僅把他們當成小技巧。把你的數據模型直接映射到你的對象,這會使得問題更簡單,并且通常也不需要用到鏈接了。尤其是,當你考慮到 MongoDB 允許你對內嵌文檔和數組的字段進行查詢和索引時,效果特別明顯。
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#大而全還是小而專的集合)大而全還是小而專的集合?
由于對集合沒做任何的強制要求,完全可以在系統中用一個混合了各種文檔的集合,但這絕對是個非常爛的主意。大多數 MongoDB 系統都采用了和關系型數據庫類似的結構,分成幾個集合。換而言之,如果在關系型數據庫中是一個表,那么在 MongoDB 中會被作成一個集合 (many-to-many join tables being an important exception as well as tables that exist only to enable one to many relationships with simple entities)。
當你把內嵌文檔考慮進來的時候,這個話題會變的更有趣。常見的例子就是博客。你是應該分成一個?`posts`?集合和一個`comments`?集合呢,還是應該每個?`post`?下面嵌入一個?`comments`?數組? 先不考慮那個 16MB 文檔大小限制 (?_哈姆雷特_全文也沒超過 200KB,所以你的博客是有多人氣?),許多開發者都喜歡把東西劃分開來。這樣更簡潔更明確,給你更好的性能。MongoDB 的靈活架構允許你把這兩種方式結合起來,你可以把評論放在獨立的集合中,同時在博客帖子下嵌入一小部分評論 (比如說最新評論) ,以便和帖子一同顯示。這遵守以下的規則,就是你到想在一次查詢中獲取到什么內容。
這沒有硬性規定(好吧,除了16MB限制)。嘗試用不同的方法解決問題,你會知道什么能用什么不能用。
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#小結-3)小結
本章目標是提供一些對你在 MongoDB 中數據建模有幫助的指導, 一個新起點,如果愿意你可以這樣認為。在一個面向文檔系統中建模,和在面向關系世界中建模,是不一樣的,但也沒多少不同。你能得到更多的靈活性并且只有一個約束,而對于新系統,一切都很完美。你唯一會做錯的就是你不去嘗試。