MongoDB 關系
MongoDB 的關系表示多個文檔之間在邏輯上的相互聯系。
文檔間可以通過嵌入和引用來建立聯系。
MongoDB 中的關系可以是:
- 1:1 (1對1)
- 1: N (1對多)
- N: 1 (多對1)
- N: N (多對多)
接下來我們來考慮下用戶與用戶地址的關系。
一個用戶可以有多個地址,所以是一對多的關系。
以下是 user 文檔的簡單結構:
{ "_id":ObjectId("52ffc33cd85242f436000001"), "name": "Tom Hanks", "contact": "987654321", "dob": "01-01-1991" }
以下是 address 文檔的簡單結構:
{ "_id":ObjectId("52ffc4a5d85242602e000000"), "building": "22 A, Indiana Apt", "pincode": 123456, "city": "Los Angeles", "state": "California" }
嵌入式關系
使用嵌入式方法,我們可以把用戶地址嵌入到用戶的文檔中:
"_id":ObjectId("52ffc33cd85242f436000001"), "contact": "987654321", "dob": "01-01-1991", "name": "Tom Benzamin", "address": [ { "building": "22 A, Indiana Apt", "pincode": 123456, "city": "Los Angeles", "state": "California" }, { "building": "170 A, Acropolis Apt", "pincode": 456789, "city": "Chicago", "state": "Illinois" }] }
以上數據保存在單一的文檔中,可以比較容易的獲取和維護數據。 你可以這樣查詢用戶的地址:
>db.users.findOne({"name":"Tom Benzamin"},{"address":1})
注意:以上查詢中 db 和 users 表示數據庫和集合。
這種數據結構的缺點是,如果用戶和用戶地址在不斷增加,數據量不斷變大,會影響讀寫性能。
引用式關系
引用式關系是設計數據庫時經常用到的方法,這種方法把用戶數據文檔和用戶地址數據文檔分開,通過引用文檔的 id 字段來建立關系。
{ "_id":ObjectId("52ffc33cd85242f436000001"), "contact": "987654321", "dob": "01-01-1991", "name": "Tom Benzamin", "address_ids": [ ObjectId("52ffc4a5d85242602e000000"), ObjectId("52ffc4a5d85242602e000001") ] }
以上實例中,用戶文檔的 address_ids 字段包含用戶地址的對象id(ObjectId)數組。
我們可以讀取這些用戶地址的對象id(ObjectId)來獲取用戶的詳細地址信息。
這種方法需要兩次查詢,第一次查詢用戶地址的對象id(ObjectId),第二次通過查詢的id獲取用戶的詳細地址信息。
>var result = db.users.findOne({"name":"Tom Benzamin"},{"address_ids":1}) >var addresses = db.address.find({"_id":{"$in":result["address_ids"]}})
MongoDB 數據庫引用
在上一章節MongoDB關系中我們提到了MongoDB的引用來規范數據結構文檔。
MongoDB 引用有兩種:
- 手動引用(Manual References)
- DBRefs
DBRefs vs 手動引用
考慮這樣的一個場景,我們在不同的集合中 (address_home, address_office, address_mailing, 等)存儲不同的地址(住址,辦公室地址,郵件地址等)。
這樣,我們在調用不同地址時,也需要指定集合,一個文檔從多個集合引用文檔,我們應該使用 DBRefs。
使用 DBRefs
DBRef的形式:
{ $ref : , $id : , $db : }
三個字段表示的意義為:
- $ref:集合名稱
- $id:引用的id
- $db:數據庫名稱,可選參數
以下實例中用戶數據文檔使用了 DBRef, 字段 address:
{ "_id":ObjectId("53402597d852426020000002"), "address": { "$ref": "address_home", "$id": ObjectId("534009e4d852427820000002"), "$db": "w3cschoolcc"}, "contact": "987654321", "dob": "01-01-1991", "name": "Tom Benzamin" }
address DBRef 字段指定了引用的地址文檔是在 address_home 集合下的 w3cschoolcc 數據庫,id 為 534009e4d852427820000002。
以下代碼中,我們通過指定 $ref 參數(address_home 集合)來查找集合中指定id的用戶地址信息:
>var user = db.users.findOne({"name":"Tom Benzamin"}) >var dbRef = user.address >db[dbRef.$ref].findOne({"_id":(dbRef.$id)})
以上實例返回了 address_home 集合中的地址數據:
{ "_id" : ObjectId("534009e4d852427820000002"), "building" : "22 A, Indiana Apt", "pincode" : 123456, "city" : "Los Angeles", "state" : "California" }
MongoDB 覆蓋索引查詢
官方的MongoDB的文檔中說明,覆蓋查詢是以下的查詢:
- 所有的查詢字段是索引的一部分
- 所有的查詢返回字段在同一個索引中
由於所有出現在查詢中的字段是索引的一部分, MongoDB 無需在整個數據文檔中檢索匹配查詢條件和返回使用相同索引的查詢結果。
因為索引存在於RAM中,從索引中獲取數據比通過掃描文檔讀取數據要快得多。
使用覆蓋索引查詢
為了測試蓋索引查詢,使用以下 users 集合:
{ "_id": ObjectId("53402597d852426020000002"), "contact": "987654321", "dob": "01-01-1991", "gender": "M", "name": "Tom Benzamin", "user_name": "tombenzamin" }
我們在 users 集合中創建聯合索引,字段為 gender 和 user_name :
>db.users.ensureIndex({gender:1,user_name:1})
現在,該索引會覆蓋以下查詢:
>db.users.find({gender:"M"},{user_name:1,_id:0})
也就是說,對於上述查詢,MongoDB的不會去數據庫文件中查找。相反,它會從索引中提取數據,這是非常快速的數據查詢。
由於我們的索引中不包括 _id 字段,_id在查詢中會默認返回,我們可以在MongoDB的查詢結果集中排除它。
下面的實例沒有排除_id,查詢就不會被覆蓋:
>db.users.find({gender:"M"},{user_name:1})
最后,如果是以下的查詢,不能使用覆蓋索引查詢:
- 所有索引字段是一個數組 所有索引字段是一個子文檔
MongoDB 查詢分析
MongoDB 查詢分析可以確保我們建議的索引是否有效,是查詢語句性能分析的重要工具。
MongoDB 查詢分析常用函數有:explain() 和 hint()。
使用 explain()
explain 操作提供了查詢信息,使用索引及查詢統計等。有利於我們對索引的優化。
接下來我們在 users 集合中創建 gender 和 user_name 的索引:
>db.users.ensureIndex({gender:1,user_name:1}) </p> <p>現在在查詢語句中使用 explain :</p> <pre> >db.users.find({gender:"M"},{user_name:1,_id:0}).explain()
以上的 explain() 查詢返回如下結果:
{ "cursor" : "BtreeCursor gender_1_user_name_1", "isMultiKey" : false, "n" : 1, "nscannedObjects" : 0, "nscanned" : 1, "nscannedObjectsAllPlans" : 0, "nscannedAllPlans" : 1, "scanAndOrder" : false, "indexOnly" : true, "nYields" : 0, "nChunkSkips" : 0, "millis" : 0, "indexBounds" : { "gender" : [ [ "M", "M" ] ], "user_name" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ] } }
現在,我們看看這個結果集的字段:
- indexOnly: 字段為 true ,表示我們使用了索引。
- cursor:因為這個查詢使用了索引,MongoDB 中索引存儲在B樹結構中,所以這是也使用了 BtreeCursor 類型的游標。如果沒有使用索引,游標的類型是 BasicCursor。這個鍵還會給出你所使用的索引的名稱,你通過這個名稱可以查看當前數據庫下的system.indexes集合(系統自動創建,由於存儲索引信息,這個稍微會提到)來得到索引的詳細信息。
- n:當前查詢返回的文檔數量。
- nscanned/nscannedObjects:表明當前這次查詢一共掃描了集合中多少個文檔,我們的目的是,讓這個數值和返回文檔的數量越接近越好。
- millis:當前查詢所需時間,毫秒數。
- indexBounds:當前查詢具體使用的索引。
使用 hint()
雖然MongoDB查詢優化器一般工作的很不錯,但是也可以使用 hint 來強制 MongoDB 使用一個指定的索引。
這種方法某些情形下會提升性能。 一個有索引的 collection 並且執行一個多字段的查詢(一些字段已經索引了)。
如下查詢實例指定了使用 gender 和 user_name 索引字段來查詢:
>db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1})
可以使用 explain() 函數來分析以上查詢:
>db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1}).explain()
MongoDB 原子操作
mongodb不支持事務,所以,在你的項目中應用時,要注意這點。無論什么設計,都不要要求mongodb保證數據的完整性。
但是mongodb提供了許多原子操作,比如文檔的保存,修改,刪除等,都是原子操作。
所謂原子操作就是要么這個文檔保存到Mongodb,要么沒有保存到Mongodb,不會出現查詢到的文檔沒有保存完整的情況。
原子操作數據模型
考慮下面的例子,圖書館的書籍及結賬信息。
實例說明了在一個相同的文檔中如何確保嵌入字段關聯原子操作(update:更新)的字段是同步的。
book = { _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.collection.findAndModify() 方法來判斷書籍是否可結算並更新新的結算信息。
在同一個文檔中嵌入的 available 和 checkout 字段來確保這些字段是同步更新的:
db.books.findAndModify ( { query: { _id: 123456789, available: { $gt: 0 } }, update: { $inc: { available: -1 }, $push: { checkout: { by: "abc", date: new Date() } } } } )
原子操作常用命令
$set
用來指定一個鍵並更新鍵值,若鍵不存在並創建。
{ $set : { field : value } }
$unset
用來刪除一個鍵。
{ $unset : { field : 1} }
$inc
$inc可以對文檔的某個值為數字型(只能為滿足要求的數字)的鍵進行增減的操作。
{ $inc : { field : value } }
$push
用法:
{ $push : { field : value } }
把value追加到field里面去,field一定要是數組類型才行,如果field不存在,會新增一個數組類型加進去。
$pushAll
同$push,只是一次可以追加多個值到一個數組字段內。
{ $pushAll : { field : value_array } }
$pull
從數組field內刪除一個等於value值。
{ $pull : { field : _value } }
$addToSet
增加一個值到數組內,而且只有當這個值不在數組內才增加。
$pop
刪除數組的第一個或最后一個元素
{ $pop : { field : 1 } }
$rename
修改字段名稱
{ $rename : { old_field_name : new_field_name } }
$bit
位操作,integer類型
{$bit : { field : {and : 5}}}
偏移操作符
> t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] } > t.update( {'comments.by':'joe'}, {$inc:{'comments.$.votes':1}}, false, true ) > t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }
MongoDB 高級索引
考慮以下文檔集合(users ):
{ "address": { "city": "Los Angeles", "state": "California", "pincode": "123" }, "tags": [ "music", "cricket", "blogs" ], "name": "Tom Benzamin" }
以上文檔包含了 address 子文檔和 tags 數組。
索引數組字段
假設我們基於標簽來檢索用戶,為此我們需要對集合中的數組 tags 建立索引。
在數組中創建索引,需要對數組中的每個字段依次建立索引。所以在我們為數組 tags 創建索引時,會為 music、cricket、blogs三個值建立單獨的索引。
使用以下命令創建數組索引:
>db.users.ensureIndex({"tags":1})
創建索引后,我們可以這樣檢索集合的 tags 字段:
>db.users.find({tags:"cricket"})
為了驗證我們使用使用了索引,可以使用 explain 命令:
>db.users.find({tags:"cricket"}).explain()
以上命令執行結果中會顯示 "cursor" : "BtreeCursor tags_1" ,則表示已經使用了索引。
索引子文檔字段
假設我們需要通過city、state、pincode字段來檢索文檔,由於這些字段是子文檔的字段,所以我們需要對子文檔建立索引。
為子文檔的三個字段創建索引,命令如下:
>db.users.ensureIndex({"address.city":1,"address.state":1,"address.pincode":1})
一旦創建索引,我們可以使用子文檔的字段來檢索數據:
>db.users.find({"address.city":"Los Angeles"})
記住查詢表達式必須遵循指定的索引的順序。所以上面創建的索引將支持以下查詢:
>db.users.find({"address.city":"Los Angeles","address.state":"California"})
同樣支持以下查詢:
>db.users.find({"address.city":"LosAngeles","address.state":"California","address.pincode":"123"})
MongoDB 索引限制
額外開銷
每個索引占據一定的存儲空間,在進行插入,更新和刪除操作時也需要對索引進行操作。所以,如果你很少對集合進行讀取操作,建議不使用索引。
內存(RAM)使用
由於索引是存儲在內存(RAM)中,你應該確保該索引的大小不超過內存的限制。
如果索引的大小大於內存的限制,MongoDB會刪除一些索引,這將導致性能下降。
查詢限制
索引不能被以下的查詢使用:
- 正則表達式及非操作符,如 $nin, $not, 等。
- 算術運算符,如 $mod, 等。
- $where 子句
所以,檢測你的語句是否使用索引是一個好的習慣,可以用explain來查看。
索引鍵限制
從2.6版本開始,如果現有的索引字段的值超過索引鍵的限制,MongoDB中不會創建索引。
插入文檔超過索引鍵限制
如果文檔的索引字段值超過了索引鍵的限制,MongoDB不會將任何文檔轉換成索引的集合。與mongorestore和mongoimport工具類似。
最大范圍
- 集合中索引不能超過64個
- 索引名的長度不能超過128個字符
- 一個復合索引最多可以有31個字段
MongoDB ObjectId
在前面幾個章節中我們已經使用了MongoDB 的對象 Id(ObjectId)。
在本章節中,我們將了解的ObjectId的結構。
ObjectId 是一個12字節 BSON 類型數據,有以下格式:
- 前4個字節表示時間戳
- 接下來的3個字節是機器標識碼
- 緊接的兩個字節由進程id組成(PID)
- 最后三個字節是隨機數。
MongoDB中存儲的文檔必須有一個"_id"鍵。這個鍵的值可以是任何類型的,默認是個ObjectId對象。
在一個集合里面,每個文檔都有唯一的"_id"值,來確保集合里面每個文檔都能被唯一標識。
MongoDB采用ObjectId,而不是其他比較常規的做法(比如自動增加的主鍵)的主要原因,因為在多個 服務器上同步自動增加主鍵值既費力還費時。
創建新的ObjectId
使用以下代碼生成新的ObjectId:
>newObjectId = ObjectId()
上面的語句返回以下唯一生成的id:
ObjectId("5349b4ddd2781d08c09890f3")
你也可以使用生成的id來取代MongoDB自動生成的ObjectId:
>myObjectId = ObjectId("5349b4ddd2781d08c09890f4")
創建文檔的時間戳
由於 ObjectId 中存儲了 4 個字節的時間戳,所以你不需要為你的文檔保存時間戳字段,你可以通過 getTimestamp 函數來獲取文檔的創建時間:
>ObjectId("5349b4ddd2781d08c09890f4").getTimestamp()
以上代碼將返回 ISO 格式的文檔創建時間:
ISODate("2014-04-12T21:49:17Z")
ObjectId 轉換為字符串
在某些情況下,您可能需要將ObjectId轉換為字符串格式。你可以使用下面的代碼:
>new ObjectId().str
以上代碼將返回Guid格式的字符串::
5349b4ddd2781d08c09890f3
MongoDB Map Reduce
Map-Reduce是一種計算模型,簡單的說就是將大批量的工作(數據)分解(MAP)執行,然后再將結果合並成最終結果(REDUCE)。
MongoDB提供的Map-Reduce非常靈活,對於大規模數據分析也相當實用。
MapReduce 命令
以下是MapReduce的基本語法:
>db.collection.mapReduce( function() {emit(key,value);}, //map 函數 function(key,values) {return reduceFunction}, //reduce 函數 { out: collection, query: document, sort: document, limit: number } )
使用 MapReduce 要實現兩個函數 Map 函數和 Reduce 函數,Map 函數調用 emit(key, value), 遍歷 collection 中所有的記錄, 將 key 與 value 傳遞給 Reduce 函數進行處理。
Map 函數必須調用 emit(key, value) 返回鍵值對。
參數說明:
- map :映射函數 (生成鍵值對序列,作為 reduce 函數參數)。
- reduce 統計函數,reduce函數的任務就是將key-values變成key-value,也就是把values數組變成一個單一的值value。。
- out 統計結果存放集合 (不指定則使用臨時集合,在客戶端斷開后自動刪除)。
- query 一個篩選條件,只有滿足條件的文檔才會調用map函數。(query。limit,sort可以隨意組合)
- sort 和limit結合的sort排序參數(也是在發往map函數前給文檔排序),可以優化分組機制
- limit 發往map函數的文檔數量的上限(要是沒有limit,單獨使用sort的用處不大)
以下實例在集合 orders 中查找 status:"A" 的數據,並根據 cust_id 來分組,並計算 amount 的總和。
使用 MapReduce
考慮以下文檔結構存儲用戶的文章,文檔存儲了用戶的 user_name 和文章的 status 字段:
>db.posts.insert({ "post_text": "菜鳥教程,最全的技術文檔。", "user_name": "mark", "status":"active" }) WriteResult({ "nInserted" : 1 }) >db.posts.insert({ "post_text": "菜鳥教程,最全的技術文檔。", "user_name": "mark", "status":"active" }) WriteResult({ "nInserted" : 1 }) >db.posts.insert({ "post_text": "菜鳥教程,最全的技術文檔。", "user_name": "mark", "status":"active" }) WriteResult({ "nInserted" : 1 }) >db.posts.insert({ "post_text": "菜鳥教程,最全的技術文檔。", "user_name": "mark", "status":"active" }) WriteResult({ "nInserted" : 1 }) >db.posts.insert({ "post_text": "菜鳥教程,最全的技術文檔。", "user_name": "mark", "status":"disabled" }) WriteResult({ "nInserted" : 1 }) >db.posts.insert({ "post_text": "菜鳥教程,最全的技術文檔。", "user_name": "runoob", "status":"disabled" }) WriteResult({ "nInserted" : 1 }) >db.posts.insert({ "post_text": "菜鳥教程,最全的技術文檔。", "user_name": "runoob", "status":"disabled" }) WriteResult({ "nInserted" : 1 }) >db.posts.insert({ "post_text": "菜鳥教程,最全的技術文檔。", "user_name": "runoob", "status":"active" }) WriteResult({ "nInserted" : 1 })
現在,我們將在 posts 集合中使用 mapReduce 函數來選取已發布的文章(status:"active"),並通過user_name分組,計算每個用戶的文章數:
>db.posts.mapReduce( function() { emit(this.user_name,1); }, function(key, values) {return Array.sum(values)}, { query:{status:"active"}, out:"post_total" } )
以上 mapReduce 輸出結果為:
{ "result" : "post_total", "timeMillis" : 23, "counts" : { "input" : 5, "emit" : 5, "reduce" : 1, "output" : 2 }, "ok" : 1 }
結果表明,共有 5 個符合查詢條件(status:"active")的文檔, 在map函數中生成了 5 個鍵值對文檔,最后使用reduce函數將相同的鍵值分為 2 組。
具體參數說明:
- result:儲存結果的collection的名字,這是個臨時集合,MapReduce的連接關閉后自動就被刪除了。
- timeMillis:執行花費的時間,毫秒為單位
- input:滿足條件被發送到map函數的文檔個數
- emit:在map函數中emit被調用的次數,也就是所有集合中的數據總量
- ouput:結果集合中的文檔個數(count對調試非常有幫助)
- ok:是否成功,成功為1
- err:如果失敗,這里可以有失敗原因,不過從經驗上來看,原因比較模糊,作用不大
使用 find 操作符來查看 mapReduce 的查詢結果:
>db.posts.mapReduce( function() { emit(this.user_name,1); }, function(key, values) {return Array.sum(values)}, { query:{status:"active"}, out:"post_total" } ).find()
以上查詢顯示如下結果,兩個用戶 tom 和 mark 有兩個發布的文章:
{ "_id" : "mark", "value" : 4 } { "_id" : "runoob", "value" : 1 }
用類似的方式,MapReduce可以被用來構建大型復雜的聚合查詢。
Map函數和Reduce函數可以使用 JavaScript 來實現,使得MapReduce的使用非常靈活和強大。
MongoDB 全文檢索
全文檢索對每一個詞建立一個索引,指明該詞在文章中出現的次數和位置,當用戶查詢時,檢索程序就根據事先建立的索引進行查找,並將查找的結果反饋給用戶的檢索方式。
這個過程類似於通過字典中的檢索字表查字的過程。
MongoDB 從 2.4 版本開始支持全文檢索,目前支持15種語言(暫時不支持中文)的全文索引。
- danish
- dutch
- english
- finnish
- french
- german
- hungarian
- italian
- norwegian
- portuguese
- romanian
- russian
- spanish
- swedish
- turkish
啟用全文檢索
MongoDB 在 2.6 版本以后是默認開啟全文檢索的,如果你使用之前的版本,你需要使用以下代碼來啟用全文檢索:
>db.adminCommand({setParameter:true,textSearchEnabled:true})
或者使用命令:
mongod --setParameter textSearchEnabled=true
創建全文索引
考慮以下 posts 集合的文檔數據,包含了文章內容(post_text)及標簽(tags):
{ "post_text": "enjoy the mongodb articles on Runoob", "tags": [ "mongodb", "runoob" ] }
我們可以對 post_text 字段建立全文索引,這樣我們可以搜索文章內的內容:
>db.posts.ensureIndex({post_text:"text"})
使用全文索引
現在我們已經對 post_text 建立了全文索引,我們可以搜索文章中的關鍵詞 runoob:
>db.posts.find({$text:{$search:"runoob"}})
以下命令返回了如下包含 runoob 關鍵詞的文檔數據:
{ "_id" : ObjectId("53493d14d852429c10000002"), "post_text" : "enjoy the mongodb articles on Runoob", "tags" : [ "mongodb", "runoob" ] }
如果你使用的是舊版本的 MongoDB,你可以使用以下命令:
>db.posts.runCommand("text",{search:"runoob"})
使用全文索引可以提高搜索效率。
刪除全文索引
刪除已存在的全文索引,可以使用 find 命令查找索引名:
>db.posts.getIndexes()
通過以上命令獲取索引名,本例的索引名為post_text_text,執行以下命令來刪除索引:
>db.posts.dropIndex("post_text_text")
MongoDB 正則表達式
正則表達式是使用單個字符串來描述、匹配一系列符合某個句法規則的字符串。
許多程序設計語言都支持利用正則表達式進行字符串操作。
MongoDB 使用 $regex 操作符來設置匹配字符串的正則表達式。
MongoDB使用PCRE (Perl Compatible Regular Expression) 作為正則表達式語言。
不同於全文檢索,我們使用正則表達式不需要做任何配置。
考慮以下 posts 集合的文檔結構,該文檔包含了文章內容和標簽:
{ "post_text": "enjoy the mongodb articles on runoob", "tags": [ "mongodb", "runoob" ] }
使用正則表達式
以下命令使用正則表達式查找包含 runoob 字符串的文章:
>db.posts.find({post_text:{$regex:"runoob"}})
以上查詢也可以寫為:
>db.posts.find({post_text:/runoob/})
不區分大小寫的正則表達式
如果檢索需要不區分大小寫,我們可以設置 $options 為 $i。
以下命令將查找不區分大小寫的字符串 runoob:
>db.posts.find({post_text:{$regex:"runoob",$options:"$i"}})
集合中會返回所有包含字符串 runoob 的數據,且不區分大小寫:
{ "_id" : ObjectId("53493d37d852429c10000004"), "post_text" : "hey! this is my post on runoob", "tags" : [ "runoob" ] }
數組元素使用正則表達式
我們還可以在數組字段中使用正則表達式來查找內容。 這在標簽的實現上非常有用,如果你需要查找包含以 run 開頭的標簽數據(ru 或 run 或 runoob), 你可以使用以下代碼:
>db.posts.find({tags:{$regex:"run"}})
優化正則表達式查詢
- 如果你的文檔中字段設置了索引,那么使用索引相比於正則表達式匹配查找所有的數據查詢速度更快。
- 如果正則表達式是前綴表達式,所有匹配的數據將以指定的前綴字符串為開始。例如: 如果正則表達式為 ^tut ,查詢語句將查找以 tut 為開頭的字符串。
這里面使用正則表達式有兩點需要注意:
正則表達式中使用變量。一定要使用eval將組合的字符串進行轉換,不能直接將字符串拼接后傳入給表達式。否則沒有報錯信息,只是結果為空!實例如下:
var name=eval("/" + 變量值key +"/i");
以下是模糊查詢包含title關鍵詞, 且不區分大小寫:
title:eval("/"+title+"/i") // 等同於 title:{$regex:title,$Option:"$i"}
MongoDB 管理工具: Rockmongo
RockMongo是PHP5寫的一個MongoDB管理工具。
通過 Rockmongo 你可以管理 MongoDB服務,數據庫,集合,文檔,索引等等。
它提供了非常人性化的操作。類似 phpMyAdmin(PHP開發的MySql管理工具)。
Rockmongo 下載地址:http://rockmongo.com/downloads
簡介
主要特征:
- 使用寬松的New BSD License協議
- 速度快,安裝簡單
- 支持多語言(目前提供中文、英文、日文、巴西葡萄牙語、法語、德語、俄語、意大利語)
- 系統
- 可以配置多個主機,每個主機可以有多個管理員
- 需要管理員密碼才能登入操作,確保數據庫的安全性
- 服務器
- 服務器信息 (WEB服務器, PHP, PHP.ini相關指令 ...)
- 狀態
- 數據庫信息
- 數據庫
- 查詢,創建和刪除
- 執行命令和Javascript代碼
- 統計信息
- 集合(相當於表)
- 強大的查詢工具
- 讀數據,寫數據,更改數據,復制數據,刪除數據
- 查詢、創建和刪除索引
- 清空數據
- 批量刪除和更改數據
- 統計信息
- GridFS
- 查看分塊
- 下載文件
安裝
需求
- 一個能運行PHP的Web服務器,比如Apache Httpd, Nginx ...
- PHP - 需要PHP v5.1.6或更高版本,需要支持SESSION
- 為了能連接MongoDB,你需要安裝php_mongo擴展
快速安裝
- 下載安裝包
- 解壓到你的網站目錄下
- 用編輯器打開config.php,修改host, port, admins等參數
- 在瀏覽器中訪問index.php,比如說:http://localhost/rockmongo/index.php
- 使用用戶名和密碼登錄,默認為"admin"和"admin"
- 開始玩轉MongoDB!
參考文章:http://rockmongo.com/wiki/introduction?lang=zh_cn
MongoDB GridFS
GridFS 用於存儲和恢復那些超過16M(BSON文件限制)的文件(如:圖片、音頻、視頻等)。
GridFS 也是文件存儲的一種方式,但是它是存儲在MonoDB的集合中。
GridFS 可以更好的存儲大於16M的文件。
GridFS 會將大文件對象分割成多個小的chunk(文件片段),一般為256k/個,每個chunk將作為MongoDB的一個文檔(document)被存儲在chunks集合中。
GridFS 用兩個集合來存儲一個文件:fs.files與fs.chunks。
每個文件的實際內容被存在chunks(二進制數據)中,和文件有關的meta數據(filename,content_type,還有用戶自定義的屬性)將會被存在files集合中。
以下是簡單的 fs.files 集合文檔:
{ "filename": "test.txt", "chunkSize": NumberInt(261120), "uploadDate": ISODate("2014-04-13T11:32:33.557Z"), "md5": "7b762939321e146569b07f72c62cca4f", "length": NumberInt(646) }
以下是簡單的 fs.chunks 集合文檔:
{ "files_id": ObjectId("534a75d19f54bfec8a2fe44b"), "n": NumberInt(0), "data": "Mongo Binary Data" }
GridFS 添加文件
現在我們使用 GridFS 的 put 命令來存儲 mp3 文件。 調用 MongoDB 安裝目錄下bin的 mongofiles.exe工具。
打開命令提示符,進入到MongoDB的安裝目錄的bin目錄中,找到mongofiles.exe,並輸入下面的代碼:
>mongofiles.exe -d gridfs put song.mp3
GridFS 是存儲文件的數據名稱。如果不存在該數據庫,MongoDB會自動創建。Song.mp3 是音頻文件名。
使用以下命令來查看數據庫中文件的文檔:
>db.fs.files.find()
以上命令執行后返回以下文檔數據:
{ _id: ObjectId('534a811bf8b4aa4d33fdf94d'), filename: "song.mp3", chunkSize: 261120, uploadDate: new Date(1397391643474), md5: "e4f53379c909f7bed2e9d631e15c1c41", length: 10401959 }
我們可以看到 fs.chunks 集合中所有的區塊,以下我們得到了文件的 _id 值,我們可以根據這個 _id 獲取區塊(chunk)的數據:
>db.fs.chunks.find({files_id:ObjectId('534a811bf8b4aa4d33fdf94d')})
以上實例中,查詢返回了 40 個文檔的數據,意味着mp3文件被存儲在40個區塊中。
MongoDB 固定集合(Capped Collections)
MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,對於大小固定,我們可以想象其就像一個環形隊列,當集合空間用完后,再插入的元素就會覆蓋最初始的頭部的元素!
創建固定集合
我們通過createCollection來創建一個固定集合,且capped選項設置為true:
>db.createCollection("cappedLogCollection",{capped:true,size:10000})
還可以指定文檔個數,加上max:1000屬性:
>db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})
判斷集合是否為固定集合:
>db.cappedLogCollection.isCapped()
如果需要將已存在的集合轉換為固定集合可以使用以下命令:
>db.runCommand({"convertToCapped":"posts",size:10000})
以上代碼將我們已存在的 posts 集合轉換為固定集合。
固定集合查詢
固定集合文檔按照插入順序儲存的,默認情況下查詢就是按照插入順序返回的,也可以使用$natural調整返回順序。
>db.cappedLogCollection.find().sort({$natural:-1})
固定集合的功能特點
可以插入及更新,但更新不能超出collection的大小,否則更新失敗,不允許刪除,但是可以調用drop()刪除集合中的所有行,但是drop后需要顯式地重建集合。
在32位機子上一個cappped collection的最大值約為482.5M,64位上只受系統文件大小的限制。
固定集合屬性及用法
屬性
- 屬性1:對固定集合進行插入速度極快
- 屬性2:按照插入順序的查詢輸出速度極快
- 屬性3:能夠在插入最新數據時,淘汰最早的數據
用法
- 用法1:儲存日志信息
- 用法2:緩存一些少量的文檔
MongoDB 自動增長
MongoDB 沒有像 SQL 一樣有自動增長的功能, MongoDB 的 _id 是系統自動生成的12字節唯一標識。
但在某些情況下,我們可能需要實現 ObjectId 自動增長功能。
由於 MongoDB 沒有實現這個功能,我們可以通過編程的方式來實現,以下我們將在 counters 集合中實現_id字段自動增長。
使用 counters 集合
考慮以下 products 文檔。我們希望 _id 字段實現 從 1,2,3,4 到 n 的自動增長功能。
{ "_id":1, "product_name": "Apple iPhone", "category": "mobiles" }
為此,創建 counters 集合,序列字段值可以實現自動長:
>db.createCollection("counters")
現在我們向 counters 集合中插入以下文檔,使用 productid 作為 key:
{ "_id":"productid", "sequence_value": 0 }
sequence_value 字段是序列通過自動增長后的一個值。
使用以下命令插入 counters 集合的序列文檔中:
>db.counters.insert({_id:"productid",sequence_value:0})
創建 Javascript 函數
現在,我們創建函數 getNextSequenceValue 來作為序列名的輸入, 指定的序列會自動增長 1 並返回最新序列值。在本文的實例中序列名為 productid 。
>function getNextSequenceValue(sequenceName){ var sequenceDocument = db.counters.findAndModify( { query:{_id: sequenceName }, update: {$inc:{sequence_value:1}}, new:true }); return sequenceDocument.sequence_value; }
使用 Javascript 函數
接下來我們將使用 getNextSequenceValue 函數創建一個新的文檔, 並設置文檔 _id 自動為返回的序列值:
>db.products.insert({ "_id":getNextSequenceValue("productid"), "product_name":"Apple iPhone", "category":"mobiles"}) >db.products.insert({ "_id":getNextSequenceValue("productid"), "product_name":"Samsung S3", "category":"mobiles"})
就如你所看到的,我們使用 getNextSequenceValue 函數來設置 _id 字段。
為了驗證函數是否有效,我們可以使用以下命令讀取文檔:
>db.products.find()
以上命令將返回以下結果,我們發現 _id 字段是自增長的:
{ "_id" : 1, "product_name" : "Apple iPhone", "category" : "mobiles"} { "_id" : 2, "product_name" : "Samsung S3", "category" : "mobiles" }