合法關鍵字名稱
文檔中的關鍵字在命名時需要遵循下面兩個限制條件:
- "$"字元不能作為關鍵字的第一個字元
- "."字元不能被用於關鍵字中
模式設計(Schema Design)
Mongodb中的模式設計與關係型DBMS大不相同。在建立應用前很有必要來瞭解一下mongodb中的模式設計。
在關係型資料模型中,對於一個給定依賴於用例的關聯式模式,理論上是有一個正確的設計。這就是通常的第三範式(3NF).一般人認為這是為了效率考慮。
在Mongodb中,模式設計不僅僅是一個將資料模型化的方法,更是用例的模型化。對於絕大多數的用例,模式設計需要單獨定製。這有優點和缺點-用例可以得到高效的執行;但是它可能是有盲點的模式,在特定查詢下沒有關係型模式那麼優雅。
當我們設計一個模式,有一些問題我們必須回答的是:
- 相對於連結,什麼時候我們將資料內嵌(embed)? 我們的決定會暗示問題2的答案:
- 我們需要多少集合,它們都是什嗎?
- 何時需要原子操作?這些操作僅可以在一個BSON文檔範圍內執行,不能跨文檔操作。
- 我們會建立那些索引,以使查詢和更新更快?
- 我們怎樣進行分區?分區的key又是什嗎?
內嵌和連結
當設計一個Mongodb模式,一個關鍵問題就是何時使用內嵌,何時使用連結。內嵌是將對象和數組內嵌到BSON文檔裡面。連結是文檔間的引用。
在mongodb中是沒有關聯(join)的 - 在一個有著1000個伺服器叢集中分發關聯是非常困難的。內嵌有點類似“預關聯”資料。在一個文檔內部的操作對於伺服器來說很容易處理;這些操作可以非常豐富。相對的,連結必須在用戶端進行處理;應用通過發起進一步的查詢來完成這些操作。
通常來說,為了“保持”實體間的關係,應該選擇使用內嵌。在沒有使用連結時,再使用連結可能會帶來資料的複製。
集合
mongodb中的集合類似於關係型資料庫中的表。每個集合儲存了很多文檔。正如前面提到的,這些文檔的內容可以非常豐富。
一個集合中的文檔的欄位不需要顯示聲明。但是對於模式設計師來說,他應該有個概念這些欄位是什麼並且這些文檔在集合中如何被結構化。Mongodb不需要一個集合中的文檔擁有相同的結構。但是,在實際中,絕大多數集合內部的文檔都是同一類結構的。雖然在我們需要時,我們可以移走這些(一個集合的文檔擁有相同的結構);例如我們增加一個新的欄位。在這種情況下,類似於“alter table”的操作不是必須的。
原子操作
有些問題需要執行原子操作來解決。例如,對計數器加1就是常見的需要原子操作的一種用例。Mongodb還可以執行很複雜的原子操作:
atomically {
if( doc.credits > 5 ) {
doc.credits -= 5;
doc.debits += 5;
}
}
另外的例子可能是使用者註冊流程。我們絕不允許使用者註冊兩個相同的使用者名稱:
atomically {
if( exists a document with username='jane' ) {
print "username already in use please choose another";
} else {
insert a document with username='jane' in the users collection;
print("thanks you have registered as user jane.");
}
}
這裡的關鍵是原子操作的範圍/ACID內容就是這個文檔。這樣的話我們就需要保證所有在原子操作中涉及的欄位都屬於同一個文檔。
索引
Mongodb支援聲明索引。mongodb中的索引同關係型資料庫中的索引非常類似:它們為高效查詢而設計,必須顯式聲明。這樣的話,作為模式設計的一部分,我們需要考慮定義那些索引。同關係型資料庫一樣,索引可以隨後再增加 - 如果我們決定將來會增加新的索引,我們可以隨後再加。
分區
另外一個在模式設計時需要考慮的就是分區了。一個BSON文檔(它可能有一些重要內嵌欄位)僅會駐留在一個分區上面。
一個集合可以被分區。當被分區,這個集合會有一個分區關鍵字,它決定該集合如何分區到叢集中。
通常(不是必須)對分區集合的查詢會包含分區關鍵字。
需要注意的是,分區後再更改分區關鍵字非常困難。
例子
讓我們來考慮一個內容管理系統的例子。下面的例子我們使用的mongo shell文法,但是它應該也可以在任何程式設計語言中實現 - 只要使用合適的驅動就可以了。
我們的內容管理系統會儲存部落格。部落格有作者。我們還要支援對部落格進行評論和投票。我們還希望增加標籤來提供搜尋功能。
一個好的模式設計可能會使用兩個mongodb集合,一個叫做posts,另一個叫users.這就是我們的例子中要使用的。
我們的使用者有一些屬性 - 註冊時使用的使用者ID,名稱,和他們的karma。例如我們可以:
> db.users.insert( { _id : "alex", name: { first:"Alex", last:"Benisson" }, karma : 1.0 } )
_id欄位在mongodb中必須存在,並且會自動為它建立有唯一屬性的索引。這非常適合使用我們的使用者名稱作為_id.雖然我們不是必須如此;我們也可以增加使用者名稱欄位,然後讓系統自動產生_id欄位。
現在讓我們來考慮部落格。我們假定已經有一些部落格發布了。讓我們查詢一篇:
> db.posts.findOne()
{
_id : ObjectId("4e77bb3b8a3e000000004f7a"),
when : Date("2011-09-19T02:10:11.3Z",
author : "alex",
title : "No Free Lunch",
text : "This is the text of the post. It could be very long.",
tags : [ "business", "ramblings" ],
votes : 5,
voters : [ "jane", "joe", "spencer", "phyllis", "li" ],
comments : [
{ who : "jane", when : Date("2011-09-19T04:00:10.112Z"),
comment : "I agree." },
{ who : "meghan", when : Date("2011-09-20T14:36:06.958Z"),
comment : "You must be joking. etc etc ..." }
]
}
對比一下在關係型資料中如何?這種模式,你會發現很有趣。我們會首先有一個使用者集合和部落格集合。但是通常還需要增加標籤集合,投票集合,和評論集合。收集一篇部落格的全部資訊可能會有些麻煩。
這裡擷取一個完整部落格資訊,我們可以:
> db.posts.findOne( { _id : ObjectId("4e77bb3b8a3e000000004f7a") } );
查詢由“alex”撰寫的所有部落格:
> db.posts.findOne( { author : "alex" } )
如果上面的查詢使用的很多,我們可以對作者欄位建立索引:
> db.posts.ensureIndex( { author : 1 } )
整個部落格文檔可能會很大。如果僅僅擷取alex發表部落格的標題,可以這樣:
> db.posts.findOne( { author : "alex" }, { title : 1 } )
按標籤進行搜尋:
> // make and index of all tags so that the query is fast:
> db.posts.ensureIndex( { tags : 1 } )
> db.posts.find( { tags : "business" } )
查詢meghan所有的評論:
> db.posts.find( { comments.who : "meghan" } )
可以建立索引加快搜尋:
> db.posts.ensureIndex( { "comments.who" : 1 } )
我們希望一個人對一篇部落格僅能投票一次。下面的更新例子會記錄calvin的投票。由於使用了$nin子運算式,如果calvin已經投過票了,這個更新將不會更新資料庫:
> db.posts.update( { _id : ObjectId("4e77bb3b8a3e000000004f7a"),
voters : { $nin : "calvin" } },
{ votes : { $inc : 1 }, voters : { $push : "calvin" } );
上面的操作是原子的:如果多個使用者同時投票,沒有投票會被丟失。
假定我們希望顯示最新的部落格標題和作者。這種用例需要用到用戶端聯結:
> var post = db.posts.find().sort( { when : -1 } ).limit(1);
> var user = db.users.find( { _id : post.author } );
> print( post.title + " " + user.name.first + " " + user.name.last );