摘要:所以,兩個(gè)需要同時(shí)進(jìn)行的更新會(huì)迅速接連完成,此過程不會(huì)破壞文檔最新的更新會(huì)取得勝利??梢允褂迷有缘母滦薷钠鳎付▽ξ臋n中的某些字段進(jìn)行更新。
上一篇文章:MongoDB指南---5、創(chuàng)建、刪除文檔
下一篇文章:MongoDB指南---7、find簡介與查詢條件
文檔存入數(shù)據(jù)庫以后,就可以使用update方法來更新它。update有兩個(gè)參數(shù),一個(gè)是查詢文檔,用于定位需要更新的目標(biāo)文檔;另一個(gè)是修改器(modifier)文檔,用于說明要對找到的文檔進(jìn)行哪些修改。
更新操作是不可分割的:若是兩個(gè)更新同時(shí)發(fā)生,先到達(dá)服務(wù)器的先執(zhí)行,接著執(zhí)行另外一個(gè)。所以,兩個(gè)需要同時(shí)進(jìn)行的更新會(huì)迅速接連完成,此過程不會(huì)破壞文檔:最新的更新會(huì)取得“勝利”。
最簡單的更新就是用一個(gè)新文檔完全替換匹配的文檔。這適用于進(jìn)行大規(guī)模模式遷移的情況。例如,要對下面的用戶文檔做一個(gè)比較大的調(diào)整:
{ "_id" : ObjectId("4b2b9f67a1f631733d917a7a"), "name" : "joe", "friends" : 32, "enemies" : 2 }
我們希望將"friends"和"enemies"兩個(gè)字段移到"relationships"子文檔中??梢栽趕hell中改變文檔的結(jié)構(gòu),然后使用update替換數(shù)據(jù)庫中的當(dāng)前文檔:
> var joe = db.users.findOne({"name" : "joe"}); > joe.relationships = {"friends" : joe.friends, "enemies" : joe.enemies}; { "friends" : 32, "enemies" : 2 }> joe.username = joe.name; "joe" > delete joe.friends; true > delete joe.enemies; true > delete joe.name; true > db.users.update({"name" : "joe"}, joe);
現(xiàn)在,用findOne查看更新后的文檔結(jié)構(gòu)。
{ "_id" : ObjectId("4b2b9f67a1f631733d917a7a"), "username" : "joe", "relationships" : { "friends" : 32, "enemies" : 2 } }
一個(gè)常見的錯(cuò)誤是查詢條件匹配到了多個(gè)文檔,然后更新時(shí)由于第二個(gè)參數(shù)的存在就產(chǎn)生重復(fù)的"_id"值。數(shù)據(jù)庫會(huì)拋出錯(cuò)誤,任何文檔都不會(huì)更新。
例如,有好幾個(gè)文檔都有相同的"name"值,但是我們沒有意識(shí)到:
> db.people.find() {"_id" : ObjectId("4b2b9f67a1f631733d917a7b"), "name" : "joe", "age" : 65}, {"_id" : ObjectId("4b2b9f67a1f631733d917a7c"), "name" : "joe", "age" : 20}, {"_id" : ObjectId("4b2b9f67a1f631733d917a7d"), "name" : "joe", "age" : 49},·
現(xiàn)在如果第二個(gè)Joe過生日,要增加"age"的值,我們可能會(huì)這么做:
> joe = db.people.findOne({"name" : "joe", "age" : 20}); { "_id" : ObjectId("4b2b9f67a1f631733d917a7c"), "name" : "joe", "age" : 20 } > joe.age++; > db.people.update({"name" : "joe"}, joe); E11001 duplicate key on update
到底怎么了?調(diào)用update時(shí),數(shù)據(jù)庫會(huì)查找一個(gè)"name"值為"Joe"的文檔。找到的第一個(gè)是65歲的Joe。然后數(shù)據(jù)庫試著用變量joe中的內(nèi)容替換找到的文檔,但是會(huì)發(fā)現(xiàn)集合里面已經(jīng)有一個(gè)具有同樣"_id"的文檔。所以,更新就會(huì)失敗,因?yàn)?_id"值必須唯一。為了避免這種情況,最好確保更新時(shí)總是指定一個(gè)唯一文檔,例如使用"_id"這樣的鍵來匹配。對于上面的例子,這才是正確的更新方法:
> db.people.update({"_id" : ObjectId("4b2b9f67a1f631733d917a7c")}, joe)
使用"_id"作為查詢條件比使用隨機(jī)字段速度更快,因?yàn)槭峭ㄟ^"_id"建立的索引。第5章會(huì)介紹索引對更新和其他操作的影響。
3.3.2 使用修改器通常文檔只會(huì)有一部分要更新??梢允褂迷有缘母滦薷钠鳎╱pdate modifier),指定對文檔中的某些字段進(jìn)行更新。更新修改器是種特殊的鍵,用來指定復(fù)雜的更新操作,比如修改、增加或者刪除鍵,還可能是操作數(shù)組或者內(nèi)嵌文檔。
假設(shè)要在一個(gè)集合中放置網(wǎng)站的分析數(shù)據(jù),只要有人訪問頁面,就增加計(jì)數(shù)器。可以使用更新修改器原子性地完成這個(gè)增加。每個(gè)URL及對應(yīng)的訪問次數(shù)都以如下方式存儲(chǔ)在文檔中:
{ "_id" : ObjectId("4b253b067525f35f94b60a31"), "url" : "www.example.com", "pageviews" : 52 }
每次有人訪問頁面,就通過URL找到該頁面,并用"$inc"修改器增加"pageviews"的值。
> db.analytics.update({"url" : "www.example.com"}, ... {"$inc" : {"pageviews" : 1}})
現(xiàn)在,執(zhí)行一個(gè)find操作,會(huì)發(fā)現(xiàn)"pageviews"的值增加了1。
> db.analytics.find() { "_id" : ObjectId("4b253b067525f35f94b60a31"), "url" : "www.example.com", "pageviews" : 53 }
使用修改器時(shí),"_id"的值不能改變。(注意,整個(gè)文檔替換時(shí)可以改變"_id"。)其他鍵值,包括其他唯一索引的鍵,都是可以更改的。
1. "$set"修改器入門"$set"用來指定一個(gè)字段的值。如果這個(gè)字段不存在,則創(chuàng)建它。這對更新模式或者增加用戶定義的鍵來說非常方便。例如,用戶資料存儲(chǔ)在下面這樣的文檔里:
> db.users.findOne() { "_id" : ObjectId("4b253b067525f35f94b60a31"), "name" : "joe", "age" : 30, "sex" : "male", "location" : "Wisconsin" }
非常簡要的一段用戶信息。要想添加喜歡的書籍進(jìn)去,可以使用"$set":
> db.users.update({"_id" : ObjectId("4b253b067525f35f94b60a31")}, ... {"$set" : {"favorite book" : "War and Peace"}})
之后文檔就有了"favorite book"鍵。
> db.users.findOne() { "_id" : ObjectId("4b253b067525f35f94b60a31"), "name" : "joe", "age" : 30, "sex" : "male", "location" : "Wisconsin", "favorite book" : "War and Peace" }
要是用戶覺得喜歡的其實(shí)是另外一本書,"$set"又能幫上忙了:
> db.users.update({"name" : "joe"}, ... {"$set" : {"favorite book" : "Green Eggs and Ham"}})
用"$set"甚至可以修改鍵的類型。例如,如果用戶覺得喜歡很多本書,就可以將"favorit ebook"鍵的值變成一個(gè)數(shù)組:
> db.users.update({"name" : "joe"}, ... {"$set" : {"favorite book" : ... ["Cat"s Cradle", "Foundation Trilogy", "Ender"s Game"]}})
如果用戶突然發(fā)現(xiàn)自己其實(shí)不愛讀書,可以用"$unset"將這個(gè)鍵完全刪除:
> db.users.update({"name" : "joe"}, ... {"$unset" : {"favorite book" : 1}})
現(xiàn)在這個(gè)文檔就和剛開始時(shí)一樣了。
也可以用"$set"修改內(nèi)嵌文檔:
> db.blog.posts.findOne() { "_id" : ObjectId("4b253b067525f35f94b60a31"), "title" : "A Blog Post", "content" : "...", "author" : { "name" : "joe", "email" : "[email protected]" } } > db.blog.posts.update({"author.name" : "joe"}, ... {"$set" : {"author.name" : "joe schmoe"}}) > db.blog.posts.findOne() { "_id" : ObjectId("4b253b067525f35f94b60a31"), "title" : "A Blog Post", "content" : "...", "author" : { "name" : "joe schmoe", "email" : "[email protected]" } }
增加、修改或刪除鍵時(shí),應(yīng)該使用$修改器。要把"foo"的值設(shè)為"bar",常見的錯(cuò)誤做法如下:
> db.coll.update(criteria, {"foo" : "bar"})
這會(huì)事與愿違。實(shí)際上這會(huì)將整個(gè)文檔用{"foo":"bar"}替換掉。一定要使用以$開頭的修改器來修改鍵/值對。
2. 增加和減少"$inc"修改器用來增加已有鍵的值,或者該鍵不存在那就創(chuàng)建一個(gè)。對于更新分析數(shù)據(jù)、因果關(guān)系、投票或者其他有變化數(shù)值的地方,使用這個(gè)都會(huì)非常方便。
假如建立了一個(gè)游戲集合,將游戲和變化的分?jǐn)?shù)都存儲(chǔ)在里面。比如用戶玩彈球(pinball)游戲,可以插入一個(gè)包含游戲名和玩家的文檔來標(biāo)識(shí)不同的游戲:
> db.games.insert({"game" : "pinball", "user" : "joe"})
要是小球撞到了磚塊,就會(huì)給玩家加分。分?jǐn)?shù)可以隨便給,這里就把玩家得分基數(shù)約定成50好了。使用"$inc"修改器給玩家加50分:
> db.games.update({"game" : "pinball", "user" : "joe"}, ... {"$inc" : {"score" : 50}})
更新后,可以看到:
> db.games.findOne() { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "game" : "pinball", "user" : "joe", "score" : 50 }
分?jǐn)?shù)(score)鍵原來并不存在,所以"$inc"創(chuàng)建了這個(gè)鍵,并把值設(shè)定成增加量:50。
如果小球落入加分區(qū),要加10 000分。只要給"$inc"傳遞一個(gè)不同的值就好了:
> db.games.update({"game" : "pinball", "user" : "joe"}, ... {"$inc" : {"score" : 10000}})
現(xiàn)在來看看結(jié)果:
> db.games.find() { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "game" : "pinball", "user" : "joe", "score" : 10050 }
"score"鍵已經(jīng)有了,而且有一個(gè)數(shù)字類型的值,所以服務(wù)器就給這個(gè)值增加了10 000。
"$inc"與"$set"的用法類似,就是專門來增加(和減少)數(shù)字的。"$inc"只能用于整型、長整型或雙精度浮點(diǎn)型的值。要是用在其他類型的數(shù)據(jù)上就會(huì)導(dǎo)致操作失敗,例如null、布爾類型以及數(shù)字構(gòu)成的字符串,而在其他很多語言中,這些類型都會(huì)自動(dòng)轉(zhuǎn)換為數(shù)值類型。
> db.foo.insert({"count" : "1"}) > db.foo.update({}, {"$inc" : {"count" : 1}}) Cannot apply $inc modifier to non-number
另外,"$inc"鍵的值必須為數(shù)字。不能使用字符串、數(shù)組或其他非數(shù)字的值。否則就會(huì)提示“Modifier"$inc"allowed for numbers only”(修改器"$inc"只允許使用數(shù)值類型)這樣的錯(cuò)誤。要修改其他類型,應(yīng)該使用"$set"或者一會(huì)兒要講到的數(shù)組修改器。
3. 數(shù)組修改器有一大類很重要的修改器可用于操作數(shù)組。數(shù)組是常用且非常有用的數(shù)據(jù)結(jié)構(gòu):它們不 僅是可通過索引進(jìn)行引用的列表,而且還可以作為數(shù)據(jù)集(set)來用。
4. 添加元素如果數(shù)組已經(jīng)存在,"$push"會(huì)向已有的數(shù)組末尾加入一個(gè)元素,要是沒有就創(chuàng)建一個(gè)新的數(shù)組。例如,假設(shè)要存儲(chǔ)博客文章,要添加一個(gè)用于保存數(shù)組的"comments"(評論)鍵??梢韵蜻€不存在的"comments"數(shù)組添加一條評論,這個(gè)數(shù)組會(huì)被自動(dòng)創(chuàng)建,并加入一條評論:
> db.blog.posts.findOne() { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "title" : "A blog post", "content" : "..." } > db.blog.posts.update({"title" : "A blog post"}, ... {"$push" : {"comments" : ... {"name" : "joe", "email" : "[email protected]", ... "content" : "nice post."}}}) > db.blog.posts.findOne() { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "title" : "A blog post", "content" : "...", "comments" : [ { "name" : "joe", "email" : "[email protected]", "content" : "nice post." } ] }
要是還想添加一條評論,繼續(xù)使用"$push":
> db.blog.posts.update({"title" : "A blog post"}, ... {"$push" : {"comments" : ... {"name" : "bob", "email" : "[email protected]", ... "content" : "good post."}}}) > db.blog.posts.findOne() { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "title" : "A blog post", "content" : "...", "comments" : [ { "name" : "joe", "email" : "[email protected]", "content" : "nice post." }, { "name" : "bob", "email" : "[email protected]", "content" : "good post." } ] }
這是一種比較簡單的"$push"使用形式,也可以將它應(yīng)用在一些比較復(fù)雜的數(shù)組操作中。使用"$each"子操作符,可以通過一次"$push"操作添加多個(gè)值。
> db.stock.ticker.update({"_id" : "GOOG"}, ... {"$push" : {"hourly" : {"$each" : [562.776, 562.790, 559.123]}}})
這樣就可以將三個(gè)新元素添加到數(shù)組中。如果指定的數(shù)組中只含有一個(gè)元素,那這個(gè)操作就等同于沒有使用"$each"的普通"$push"操作。
如果希望數(shù)組的最大長度是固定的,那么可以將"$slice"和"$push"組合在一起使用,這樣就可以保證數(shù)組不會(huì)超出設(shè)定好的最大長度,這實(shí)際上就得到了一個(gè)最多包含N個(gè)元素的數(shù)組:
> db.movies.find({"genre" : "horror"}, ... {"$push" : {"top10" : { ... "$each" : ["Nightmare on Elm Street", "Saw"], ... "$slice" : -10}}})
這個(gè)例子會(huì)限制數(shù)組只包含最后加入的10個(gè)元素。"$slice"的值必須是負(fù)整數(shù)。
如果數(shù)組的元素?cái)?shù)量小于10("$push"之后),那么所有元素都會(huì)保留。如果數(shù)組的元素?cái)?shù)量大于10,那么只有最后10個(gè)元素會(huì)保留。因此,"$slice"可以用來在文檔中創(chuàng)建一個(gè)隊(duì)列。
最后,可以在清理元素之前使用"$sort",只要向數(shù)組中添加子對象就需要清理:
> db.movies.find({"genre" : "horror"}, ... {"$push" : {"top10" : { ... "$each" : [{"name" : "Nightmare on Elm Street", "rating" : 6.6}, ... {"name" : "Saw", "rating" : 4.3}], ... "$slice" : -10, ... "$sort" : {"rating" : -1}}}})
這樣會(huì)根據(jù)"rating"字段的值對數(shù)組中的所有對象進(jìn)行排序,然后保留前10個(gè)。注意,不能只將"$slice"或者"$sort"與"$push"配合使用,且必須使用"$each"。
5. 將數(shù)組作為數(shù)據(jù)集使用你可能想將數(shù)組作為集合使用,保證數(shù)組內(nèi)的元素不會(huì)重復(fù)。可以在查詢文檔中用"$ne"來實(shí)現(xiàn)。例如,要是作者不在引文列表中,就添加進(jìn)去,可以這么做:
> db.papers.update({"authors cited" : {"$ne" : "Richie"}}, ... {$push : {"authors cited" : "Richie"}})
也可以用"$addToSet"來實(shí)現(xiàn),要知道有些情況"$ne"根本行不通,有些時(shí)候更適合用"$addToSet"。
例如,有一個(gè)表示用戶的文檔,已經(jīng)有了電子郵件地址的數(shù)據(jù)集:
> db.users.findOne({"_id" : ObjectId("4b2d75476cc613d5ee930164")}) { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "username" : "joe", "emails" : [ "[email protected]", "[email protected]", "[email protected]" ] }
添加新地址時(shí),用"$addToSet"可以避免插入重復(fù)地址:
> db.users.update({"_id" : ObjectId("4b2d75476cc613d5ee930164")}, ... {"$addToSet" : {"emails" : "[email protected]"}}) > db.users.findOne({"_id" : ObjectId("4b2d75476cc613d5ee930164")}) { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "username" : "joe", "emails" : [ "[email protected]", "[email protected]", "[email protected]", ] } > db.users.update({"_id" : ObjectId("4b2d75476cc613d5ee930164")}, ... {"$addToSet" : {"emails" : "[email protected]"}}) > db.users.findOne({"_id" : ObjectId("4b2d75476cc613d5ee930164")}) { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "username" : "joe", "emails" : [ "[email protected]", "[email protected]", "[email protected]", "[email protected]" ] }
將"$addToSet"和"$each"組合起來,可以添加多個(gè)不同的值,而用"$ne"和"$push"組合就不能實(shí)現(xiàn)。例如,想一次添加多個(gè)郵件地址,就可以使用這些修改器:
> db.users.update({"_id" : ObjectId("4b2d75476cc613d5ee930164")}, {"$addToSet" : ... {"emails" : {"$each" : ... ["[email protected]", "[email protected]", "[email protected]"]}}}) > db.users.findOne({"_id" : ObjectId("4b2d75476cc613d5ee930164")}) { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "username" : "joe", "emails" : [ "[email protected]", "[email protected]", "[email protected]", "[email protected]" "[email protected]" "[email protected]" ] }6. 刪除元素
有幾個(gè)從數(shù)組中刪除元素的方法。若是把數(shù)組看成隊(duì)列或者棧,可以用"$pop",這個(gè)修改器可以從數(shù)組任何一端刪除元素。{"$pop":{"key":1}}從數(shù)組末尾刪除一個(gè)元素,{"$pop":{"key":-1}}則從頭部刪除。
有時(shí)需要基于特定條件來刪除元素,而不僅僅是依據(jù)元素位置,這時(shí)可以使用"$pull"。例如,有一個(gè)無序的待完成事項(xiàng)列表:
> db.lists.insert({"todo" : ["dishes", "laundry", "dry cleaning"]})
要是想把洗衣服(laundry)放到第一位,可以從列表中先把它刪掉:
> db.lists.update({}, {"$pull" : {"todo" : "laundry"}})
通過查找,會(huì)發(fā)現(xiàn)只有兩個(gè)元素了:
> db.lists.find() { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "todo" : [ "dishes", "dry cleaning" ] }
"$pull"會(huì)將所有匹配的文檔刪除,而不是只刪除一個(gè)。對數(shù)組[1,1,2,1]執(zhí)行pull 1,結(jié)果得到 只有一個(gè)元素的數(shù)組2。
數(shù)組操作符只能用于包含數(shù)組值的鍵。例如,不能將一個(gè)整數(shù)插入數(shù)組,也不能將一個(gè)字符串從數(shù)組中彈出。要修改標(biāo)量值,使用"$set"或者"$inc"。
若是數(shù)組有多個(gè)值,而我們只想對其中的一部分進(jìn)行操作,就需要一些技巧。有兩種方法操作數(shù)組中的值:通過位置或者定位操作符("$")。
數(shù)組下標(biāo)都是以0開頭的,可以將下標(biāo)直接作為鍵來選擇元素。例如,這里有個(gè)文檔,其中包含由內(nèi)嵌文檔組成的數(shù)組,比如包含評論的博客文章。
> db.blog.posts.findOne() { "_id" : ObjectId("4b329a216cc613d5ee930192"), "content" : "...", "comments" : [ { "comment" : "good post", "author" : "John", "votes" : 0 }, { "comment" : "i thought it was too short", "author" : "Claire", "votes" : 3 }, { "comment" : "free watches", "author" : "Alice", "votes" : -1 } ] }
如果想增加第一個(gè)評論的投票數(shù)量,可以這么做:
> db.blog.update({"post" : post_id}, ... {"$inc" : {"comments.0.votes" : 1}})
但是很多情況下,不預(yù)先查詢文檔就不能知道要修改的數(shù)組的下標(biāo)。為了克服這個(gè)困難,MongoDB提供了定位操作符"$",用來定位查詢文檔已經(jīng)匹配的數(shù)組元素,并進(jìn)行更新。例如,要是用戶John把名字改成了Jim,就可以用定位符替換他在評論中的名字:
db.blog.update({"comments.author" : "John"}, ... {"$set" : {"comments.$.author" : "Jim"}})
定位符只更新第一個(gè)匹配的元素。所以,如果John發(fā)表了多條評論,那么他的名字只在第一條評論中改變。
8. 修改器速度有的修改器運(yùn)行比較快。$inc能就地修改,因?yàn)椴恍枰淖兾臋n的大小,只需要將 鍵的值修改一下(對文檔大小的改變非常?。苑浅?臁6鴶?shù)組修改器可能會(huì)改變文檔的大小,就會(huì)慢一些("$set"能在文檔大小不發(fā)生變化時(shí)立即修改它,否則性能也會(huì)有所下降)。
將文檔插入到MongoDB中時(shí),依次插入的文檔在磁盤上的位置是相鄰的。因此,如果一個(gè)文檔變大了,原先的位置就放不下這個(gè)文檔了,這個(gè)文檔就會(huì)被移動(dòng)到集合中的另一個(gè)位置。
可以在實(shí)際操作中看到這種變化。創(chuàng)建一個(gè)包含幾個(gè)文檔的集合,對某個(gè)位于中間的文檔進(jìn)行修改,使其尺寸變大。然后會(huì)發(fā)現(xiàn)這個(gè)文檔被移動(dòng)到了集合的尾部:
> db.coll.insert({"x" :"a"}) > db.coll.insert({"x" :"b"}) > db.coll.insert({"x" :"c"}) > db.coll.find() { "_id" : ObjectId("507c3581d87d6a342e1c81d3"), "x" : "a" } { "_id" : ObjectId("507c3583d87d6a342e1c81d4"), "x" : "b" } { "_id" : ObjectId("507c3585d87d6a342e1c81d5"), "x" : "c" } > db.coll.update({"x" : "b"}, {$set: {"x" : "bbb"}}) > db.coll.find() { "_id" : ObjectId("507c3581d87d6a342e1c81d3"), "x" : "a" } { "_id" : ObjectId("507c3585d87d6a342e1c81d5"), "x" : "c" } { "_id" : ObjectId("507c3583d87d6a342e1c81d4"), "x" : "bbb" }
MongoDB不得不移動(dòng)一個(gè)文檔時(shí),它會(huì)修改集合的填充因子(padding factor)。填充因子是MongoDB為每個(gè)新文檔預(yù)留的增長空間??梢赃\(yùn)行db.coll.stats()查看填充因子。執(zhí)行上面的更新之前,"paddingFactor"字段的值是1:根據(jù)實(shí)際的文檔大小,為每個(gè)新文檔分配精確的空間,不預(yù)留任何增長空間,如圖3-1所示。讓其中一個(gè)文檔增大之后,再次運(yùn)行這個(gè)命令(如圖3-2所示),會(huì)發(fā)現(xiàn)填充因子增加到了1.5:為每個(gè)新文檔預(yù)留其一半大小的空間作為增長空間,如圖3-2所示。如果隨后的更新導(dǎo)致了更多次的文檔移動(dòng),填充因子會(huì)持續(xù)變大(雖然不會(huì)像第一次移動(dòng)時(shí)的變化那么大)。如果不再有文檔移動(dòng),填充因子的值會(huì)緩慢降低,如圖3-3所示。
圖3-1 最初,文檔之間沒有多余的空間
圖3-2 如果一個(gè)文檔因?yàn)轶w積變大而不得不進(jìn)行移動(dòng),它原先占用的空間就閑置了,而且填充因子會(huì)增加
圖3-3 之后插入的新文檔都會(huì)擁有填充因子指定大小的增長空間。如果在之后的插入中不再發(fā)生文檔移動(dòng),填充因子會(huì)逐漸變小
移動(dòng)文檔是非常慢的。MongoDB必須將文檔原先所占的空間釋放掉,然后將文檔寫入另一片空間。因此,應(yīng)該盡量讓填充因子的值接近1。無法手動(dòng)設(shè)定填充因子的值(除非是要對集合進(jìn)行壓縮,參見18.4節(jié)),但是可以設(shè)計(jì)一種不依賴于文檔、可以任意增長的模式。第8章會(huì)詳細(xì)介紹模式設(shè)計(jì)的相關(guān)內(nèi)容。
下面用一個(gè)簡單的程序來展示原地更新和文檔移動(dòng)的速度差別。下面的程序插入了一個(gè)只包含一個(gè)鍵的文檔,并且對這個(gè)鍵的值進(jìn)行了100 000次增加:
> db.tester.insert({"x" : 1}) > var timeInc = function() { ... var start = (new Date()).getTime(); ... ... for (var i=0; i<100000; i++) { ... db.tester.update({}, {"$inc" : {"x" : 1}}); ... db.getLastError(); ... } ... ... var timeDiff = (new Date()).getTime() - start; ... print("Updates took: "+timeDiff+"ms"); ... } > timeInc()
在MacBook Air上,總共花費(fèi)了7.33秒。也就是每秒超過13 000次更新。現(xiàn)在,使用"$push"向一個(gè)只有一個(gè)鍵的數(shù)組中插入新數(shù)據(jù),重復(fù)100 000次。將上面例子中用于更新文檔的代碼修改為:
... db.tester.update({}, {"$push" : {"x" : 1}})
這個(gè)程序運(yùn)行時(shí)間為67.58秒,每秒少于1500次更新。
使用"$push"以及其他一些數(shù)組修改器是非常好的,而且通常是必要的,但是,在進(jìn)行類似的更新時(shí),需要好好權(quán)衡一下。如果"$push"成為了瓶頸,那么將一個(gè)內(nèi)嵌文檔取出放入一個(gè)多帶帶的集合中,手動(dòng)填充,或者使用第8章將要介紹的其他某項(xiàng)技術(shù),都很值得。
寫作本書時(shí),MongoDB仍然不能很好地重用空白空間,因此頻繁移動(dòng)文檔會(huì)產(chǎn)生大量空的數(shù)據(jù)文件。如果有太多不能重用的空白空間,你會(huì)經(jīng)常在日志中看到如下信息:
Thu Apr 5 01:12:28 [conn124727] info DFM::findAll(): extent a:7f18dc00 was empty, skipping ahead
這就是說,執(zhí)行查詢時(shí),MongoDB會(huì)在整個(gè)范圍(entire extent,可以在附錄B中查看相關(guān)定義。簡單來說,它就是集合的一個(gè)子集)內(nèi)進(jìn)行查找,卻找不到任何文檔:這只是個(gè)空白空間。這個(gè)消息提示本身沒什么影響,但是它指出你當(dāng)前擁有太多的碎片,可能需要進(jìn)行壓縮。
如果你的模式在進(jìn)行插入和刪除時(shí)會(huì)進(jìn)行大量的移動(dòng)或者是經(jīng)常打亂數(shù)據(jù),可以使用usePowerOf2Sizes選項(xiàng)以提高磁盤復(fù)用率??梢酝ㄟ^collMod命令來設(shè)定這個(gè)選項(xiàng):
> db.runCommand({"collMod" : collectionName, "usePowerOf2Sizes" : true})
這個(gè)集合之后進(jìn)行的所有空間分配,得到的塊大小都是2的冪。由于這個(gè)選項(xiàng)會(huì)導(dǎo)致初始空間分配不再那么高效,所以應(yīng)該只在需要經(jīng)常打亂數(shù)據(jù)的集合上使用。在一個(gè)只進(jìn)行插入或者原地更新的集合上使用這個(gè)選項(xiàng),會(huì)導(dǎo)致寫入速度變慢。
如果在這個(gè)命令中指定"usePowerOf2Sizes"選項(xiàng)的值為false,就會(huì)關(guān)閉這種特殊分配機(jī)制。這個(gè)選項(xiàng)只會(huì)影響之后新分配的記錄,因此,在已有的集合上運(yùn)行這個(gè)命令或者是更改這個(gè)選項(xiàng)的值,不會(huì)對現(xiàn)有數(shù)據(jù)產(chǎn)生影響。
upsert是一種特殊的更新。要是沒有找到符合更新條件的文檔,就會(huì)以這個(gè)條件和更新文檔為基礎(chǔ)創(chuàng)建一個(gè)新的文檔。如果找到了匹配的文檔,則正常更新。upsert非常方便,不必預(yù)置集合,同一套代碼既可以用于創(chuàng)建文檔又可以用于更新文檔。
我們回過頭看看那個(gè)記錄網(wǎng)站頁面訪問次數(shù)的例子。要是沒有upsert,就得試著查詢URL,沒有找到就得新建一個(gè)文檔,找到的話就增加訪問次數(shù)。要是把這個(gè)寫成JavaScript程序,會(huì)是下面這樣的:
// 檢查這個(gè)頁面是否有一個(gè)文檔 blog = db.analytics.findOne({url : "/blog"}) // 如果有,就將視圖數(shù)加/并保存 if (blog) { blog.pageviews++; db.analytics.save(blog); } // 否則為這個(gè)頁面創(chuàng)建一個(gè)新文檔 else { db.analytics.save({url : "/blog", pageviews : 1}) }
這就是說如果有人訪問頁面,我們得先對數(shù)據(jù)庫進(jìn)行查詢,然后選擇更新或者插入。要是多個(gè)進(jìn)程同時(shí)運(yùn)行這段代碼,還會(huì)遇到同時(shí)對給定URL插入多個(gè)文檔這樣的競態(tài)條件。
要是使用upsert,既可以避免競態(tài)問題,又可以縮減代碼量(update的第3個(gè)參數(shù)表示這是個(gè)upsert):
db.analytics.update({"url" : "/blog"}, {"$inc" : {"pageviews" : 1}}, true)
這行代碼和之前的代碼作用完全一樣,但它更高效,并且是原子性的!創(chuàng)建新文檔會(huì)將條件文檔作為基礎(chǔ),然后對它應(yīng)用修改器文檔。
例如,要是執(zhí)行一個(gè)匹配鍵并增加對應(yīng)鍵值的upsert操作,會(huì)在匹配的文檔上進(jìn)行增加:
> db.users.update({"rep" : 25}, {"$inc" : {"rep" : 3}}, true) > db.users.findOne() { "_id" : ObjectId("4b3295f26cc613d5ee93018f"), "rep" : 28 }
upsert創(chuàng)建一個(gè)"rep"值為25的文檔,隨后將這個(gè)值加3,最后得到"rep"為28的文檔。要是不指定upsert選項(xiàng),{"rep":25}不會(huì)匹配任何文檔,也就不會(huì)對集合進(jìn)行任何更新。
要是再次運(yùn)行這個(gè)upsert(條件為{"rep":25}),還會(huì)創(chuàng)建一個(gè)新文檔。這是因?yàn)闆]有文檔滿足匹配條件(唯一一個(gè)文檔的"rep"值是28)。
有時(shí),需要在創(chuàng)建文檔的同時(shí)創(chuàng)建字段并為它賦值,但是在之后的所有更新操作中,這個(gè)字段的值都不再改變。這就是"$setOnInsert"的作用。"$setOnInsert"只會(huì)在文檔插入時(shí)設(shè)置字段的值。因此,實(shí)際使用中可以這么做:
> db.users.update({}, {"$setOnInsert" : {"createdAt" : new Date()}}, true) > db.users.findOne() { "_id" : ObjectId("512b8aefae74c67969e404ca"), "createdAt" : ISODate("2013-02-25T16:01:50.742Z") }
如果再次運(yùn)行這個(gè)更新,會(huì)匹配到這個(gè)已存在的文檔,所以不會(huì)再插入文檔,因此"createdAt"字段的值也不會(huì)改變:
> db.users.update({}, {"$setOnInsert" : {"createdAt" : new Date()}}, true) > db.users.findOne() { "_id" : ObjectId("512b8aefae74c67969e404ca"), "createdAt" : ISODate("2013-02-25T16:01:50.742Z") }
注意,通常不需要保留"createdAt"這樣的字段,因?yàn)镺bjectIds里包含了一個(gè)用于標(biāo)明文檔創(chuàng)建時(shí)間的時(shí)間戳。但是,在預(yù)置或者初始化計(jì)數(shù)器時(shí),或者是對于不使用ObjectIds的集合來說,"$setOnInsert"是非常有用的。
save shell幫助程序save是一個(gè)shell函數(shù),如果文檔不存在,它會(huì)自動(dòng)創(chuàng)建文檔;如果文檔存在,它就更新這個(gè)文檔。它只有一個(gè)參數(shù):文檔。要是這個(gè)文檔含有"_id"鍵,save會(huì)調(diào)用upsert。否則,會(huì)調(diào)用insert。如果在Shell中使用這個(gè)函數(shù),就可以非常方便地對文檔進(jìn)行快速修改。
> var x = db.foo.findOne() > x.num = 42 42 > db.foo.save(x)
要是不用save的話,最后一行代碼看起來就會(huì)比較繁瑣了,比如db.foo.up date({"_id" : x._id}, x)。
3.3.4 更新多個(gè)文檔默認(rèn)情況下,更新只能對符合匹配條件的第一個(gè)文檔執(zhí)行操作。要是有多個(gè)文檔符合條件,只有第一個(gè)文檔會(huì)被更新,其他文檔不會(huì)發(fā)生變化。要更新所有匹配的文檔,可以將update的第4個(gè)參數(shù)設(shè)置為true。
update的行為以后可能會(huì)發(fā)生變化(服務(wù)器可能默認(rèn)會(huì)更新所有匹配的文檔,只有第4個(gè)參數(shù)為false才會(huì)只更新一個(gè)),所以建議每次都顯式表明要不要做多文檔更新。
這樣不但更明確地指定了update的行為,而且可以在默認(rèn)行為發(fā)生變化時(shí)正常運(yùn)行。
多文檔更新對模式遷移非常有用,還可以在對特定用戶發(fā)布新功能時(shí)使用。例如,要送給在個(gè)指定日期過生日的所有用戶一份禮物,就可以使用多文檔更新,將"gift"增加到他們的賬號:
> db.users.update({"birthday" : "10/13/1978"}, ... {"$set" : {"gift" : "Happy Birthday!"}}, false, true)
這樣就給生日為1978年10月13日的所有用戶文檔添加了"gift"鍵。
想要知道多文檔更新到底更新了多少文檔,可以運(yùn)行g(shù)etLastError命令(可以理解為“返回最后一次操作的相關(guān)信息”)。鍵"n"的值就是被更新文檔的數(shù)量。
> db.count.update({x : 1}, {$inc : {x : 1}}, false, true) > db.runCommand({getLastError : 1}) { "err" : null, "updatedExisting" : true, "n" : 5, "ok" : true }
這里"n"為5,說明有5個(gè)文檔被更新了。"updatedExisting"為true,說明是對已有的文檔進(jìn)行更新。
3.3.5 返回被更新的文檔調(diào)用getLastError僅能獲得關(guān)于更新的有限信息,并不能返回被更新的文檔??梢酝ㄟ^findAndModify命令得到被更新的文檔。這對于操作隊(duì)列以及執(zhí)行其他需要進(jìn)行原子性取值和賦值的操作來說,十分方便。
假設(shè)我們有一個(gè)集合,其中包含以一定順序運(yùn)行的進(jìn)程。其中每個(gè)進(jìn)程都用如下形式的文檔表示:
{ "_id" : ObjectId(), "status" : state, "priority" : N }
"status"是一個(gè)字符串,它的值可以是"READY"、"RUNNING"或"DONE"。需要找到狀態(tài)為"READY"具有最高優(yōu)先級的任務(wù),運(yùn)行相應(yīng)的進(jìn)程函數(shù),然后將其狀態(tài)更新為"DONE"。也可能需要查詢已經(jīng)就緒的進(jìn)程,按照優(yōu)先級排序,然后將優(yōu)先級最高的進(jìn)程的狀態(tài)更新為"RUNNING"。完成了以后,就把狀態(tài)改為"DONE"。就像下面這樣:
var cursor = db.processes.find({"status" : "READY"}); ps = cursor.sort({"priority" : -1}).limit(1).next(); db.processes.update({"_id" : ps._id}, {"$set" : {"status" : "RUNNING"}}); do_something(ps); db.processes.update({"_id" : ps._id}, {"$set" : {"status" : "DONE"}});
這個(gè)算法不是很好,可能會(huì)導(dǎo)致競態(tài)條件。假設(shè)有兩個(gè)線程正在運(yùn)行。A線程讀取了文檔,B線程在A將文檔狀態(tài)改為"RUNNING"之前也讀取了同一個(gè)文檔,這樣兩個(gè)線程會(huì)運(yùn)行相同的處理過程。雖然可以在更新查詢中進(jìn)行狀態(tài)檢查來避免這一問題,但是十分復(fù)雜:
var cursor = db.processes.find({"status" : "READY"}); cursor.sort({"priority" : -1}).limit(1); while ((ps = cursor.next()) != null) { ps.update({"_id" : ps._id, "status" : "READY"}, {"$set" : {"status" : "RUNNING"}}); var lastOp = db.runCommand({getlasterror : 1}); if (lastOp.n == 1) { do_something(ps); db.processes.update({"_id" : ps._id}, {"$set" : {"status" : "DONE"}}) break; } cursor = db.processes.find({"status" : "READY"}); cursor.sort({"priority" : -1}).limit(1); }
這樣也有問題。因?yàn)橛邢扔泻?,很可能一個(gè)線程處理了所有任務(wù),而另外一個(gè)就傻傻地呆在那里。A線程可能會(huì)一直占用著進(jìn)程,B線程試著搶占失敗后,就讓A線程自己處理所有任務(wù)了。
遇到類似這樣的情況時(shí),findAndModify就可大顯身手了。findAndModify能夠在一個(gè)操作中返回匹配結(jié)果并且進(jìn)行更新。在本例中,處理過程如下所示:
> ps = db.runCommand({"findAndModify" : "processes", ... "query" : {"status" : "READY"}, ... "sort" : {"priority" : -1}, ... "update" : {"$set" : {"status" : "RUNNING"}}) { "ok" : 1, "value" : { "_id" : ObjectId("4b3e7a18005cab32be6291f7"), "priority" : 1, "status" : "READY" } }
注意,返回文檔中的狀態(tài)仍然為"READY",因?yàn)閒indAndModify返回的是修改之前的文檔。要是再在集合上進(jìn)行一次查詢,會(huì)發(fā)現(xiàn)這個(gè)文檔的"status"已經(jīng)更新成了"RUNNING":
> db.processes.findOne({"_id" : ps.value._id}) { "_id" : ObjectId("4b3e7a18005cab32be6291f7"), "priority" : 1, "status" : "RUNNING" }
這樣的話,程序就變成了下面這樣:
ps = db.runCommand({"findAndModify" : "processes", "query" : {"status" : "READY"}, "sort" : {"priority" : -1}, "update" : {"$set" : {"status" : "RUNNING"}}}).value do_something(ps) db.process.update({"_id" : ps._id}, {"$set" : {"status" : "DONE"}})
findAndModify可以使用"update"鍵也可以使用"remove"鍵。"remove"鍵表示將匹配的文檔從集合里面刪除。例如,現(xiàn)在不用更新狀態(tài)了,而是直接刪掉,就可以像下面這樣:
ps = db.runCommand({"findAndModify" : "processes", "query" : {"status" : "READY"}, "sort" : {"priority" : -1}, "remove" : true}).value do_something(ps)
findAndModify命令有很多可以使用的字段。
findAndModify
字符串,集合名。
query
查詢文檔,用于檢索文檔的條件。
sort
排序結(jié)果的條件。
update
修改器文檔,用于對匹配的文檔進(jìn)行更新(update和remove必須指定一個(gè))。
remove
布爾類型,表示是否刪除文檔(remove和update必須指定一個(gè))。
new
布爾類型,表示返回更新前的文檔還是更新后的文檔。默認(rèn)是更新前的文檔。
fields
文檔中需要返回的字段(可選)。
upsert
布爾類型,值為true時(shí)表示這是一個(gè)upsert。默認(rèn)為false。
"update"和"remove"必須有一個(gè),也只能有一個(gè)。要是沒有匹配的文檔,這個(gè)命令會(huì)返回一個(gè)錯(cuò)誤。
3.4 寫入安全機(jī)制寫入安全(Write Concern)是一種客戶端設(shè)置,用于控制寫入的安全級別。默認(rèn)情況下,插入、刪除和更新都會(huì)一直等待數(shù)據(jù)庫響應(yīng)(寫入是否成功),然后才會(huì)繼續(xù)執(zhí)行。通常,遇到錯(cuò)誤時(shí),客戶端會(huì)拋出一個(gè)異常(有些語言中可能不叫“異?!保贿^實(shí)質(zhì)上都是類似的東西)。
有一些選項(xiàng)可以用于精確控制需要應(yīng)用程序等待的內(nèi)容。兩種最基本的寫入安全機(jī)制是應(yīng)答式寫入(acknowledged wirte)和非應(yīng)答式寫入(unacknowledged write)。應(yīng)答式寫入是默認(rèn)的方式:數(shù)據(jù)庫會(huì)給出響應(yīng),告訴你寫入操作是否成功執(zhí)行。非應(yīng)答式寫入不返回任何響應(yīng),所以無法知道寫入是否成功。
通常來說,應(yīng)用程序應(yīng)該使用應(yīng)答式寫入。但是,對于一些不是特別重要的數(shù)據(jù)(比如日志或者是批量加載數(shù)據(jù)),你可能不愿意為了自己不關(guān)心的數(shù)據(jù)而等待數(shù)據(jù)庫響應(yīng)。在這種情況下,可以使用非應(yīng)答式寫入。
盡管非應(yīng)答式寫入不返回?cái)?shù)據(jù)庫錯(cuò)誤,但是這不代表應(yīng)用程序不需要做錯(cuò)誤檢查。如果嘗試向已經(jīng)關(guān)閉的套接字(socket)執(zhí)行寫入,或者寫入套接字時(shí)發(fā)生了錯(cuò)誤,都會(huì)引起異常。
使用非應(yīng)答式寫入時(shí),一種經(jīng)常被忽視的錯(cuò)誤是插入無效數(shù)據(jù)。比如,如果試圖插入兩個(gè)具有相同"_id"字段的文檔,shell就會(huì)拋出異常:
> db.foo.insert({"_id" : 1}) > db.foo.insert({"_id" : 1}) E11000 duplicate key error index: test.foo.$_id_ dup key: { : 1.0 }
如果第二次插入時(shí)使用的是非應(yīng)答式寫入,那么第二次插入就不會(huì)拋出異常。鍵重復(fù)異常是一種非常常見的錯(cuò)誤,還有其他很多類似的錯(cuò)誤,比如無效的修改器或者是磁盤空間不足等。
shell與客戶端程序?qū)Ψ菓?yīng)答式寫入的實(shí)際支持并不一樣:shell在執(zhí)行非應(yīng)答式寫入后,會(huì)檢查最后一個(gè)操作是否成功,然后才會(huì)向用戶輸出提示信息。因此,如果在集合上執(zhí)行了一系列無效操作,最后又執(zhí)行了一個(gè)有效操作,shell并不會(huì)提示有錯(cuò)誤發(fā)生。
> db.foo.insert({"_id" : 1}); db.foo.insert({"_id" : 1}); db.foo.count()
可以調(diào)用getLastError 手動(dòng)強(qiáng)制在shell 中進(jìn)行檢查,這一操作會(huì)檢查最后一次 操作中的錯(cuò)誤。
> db.foo.insert({"_id" : 1}); db.foo.insert({"_id" : 1}); print( ... db.getLastError()); db.foo.count() E11000 duplicate key error index: test.foo.$_id_ dup key: { : 1.0 } 1
編寫需要在shell中執(zhí)行的腳本時(shí),這是非常有用的。
事實(shí)上,還有其他一些寫入安全機(jī)制,第11章會(huì)講述多臺(tái)服務(wù)器之間的寫入安全,第19章會(huì)講述寫入提交。
2012年,默認(rèn)的寫入安全機(jī)制改變了,所以,遺留代碼的行為可能會(huì)與預(yù)期不一致。在此之前,默認(rèn)的寫入是非應(yīng)答式的。
幸好,很容易得知當(dāng)前代碼是在默認(rèn)的寫入安全機(jī)制發(fā)生變化之前寫的還是之后寫的:默認(rèn)的寫入機(jī)制變?yōu)榘踩珜懭胫?,所有?qū)動(dòng)程序都開始使用MongoClient這個(gè)類。如果程序使用的連接對象是Mongo或者Connection或者其他內(nèi)容,那么這段程序使用的就是舊的、默認(rèn)不安全的API。在默認(rèn)寫入安全機(jī)制發(fā)生變化之前,任何語言都沒有使用MongoClient作為類名,所以,如果你的代碼使用了這個(gè)類名,說明你的代碼是寫入安全的。 如果使用的連接不是MongoClient,應(yīng)在必要時(shí)將舊代碼中的非應(yīng)答式寫入改成應(yīng)答式寫入.
上一篇文章:MongoDB指南---5、創(chuàng)建、刪除文檔
下一篇文章:MongoDB指南---7、find簡介與查詢條件
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/44012.html
摘要:如將構(gòu)造函數(shù)作為函數(shù)進(jìn)行調(diào)用即不包括的方式,返回的是日期的字符串表示,而非日期對象。如果不注意這一點(diǎn),沒有始終使用日期構(gòu)造函數(shù),將得到一堆混亂的日期對象和日期的字符串。關(guān)于日期類的完整解釋,以及構(gòu)造函數(shù)的參數(shù)格式,參見規(guī)范節(jié)。 上一篇文章:MongoDB指南---2、MongoDB基礎(chǔ)知識(shí)-文檔、集合、數(shù)據(jù)庫、客戶端下一篇文章:MongoDB指南---4、MongoDB基礎(chǔ)知識(shí)-使用M...
摘要:如將構(gòu)造函數(shù)作為函數(shù)進(jìn)行調(diào)用即不包括的方式,返回的是日期的字符串表示,而非日期對象。如果不注意這一點(diǎn),沒有始終使用日期構(gòu)造函數(shù),將得到一堆混亂的日期對象和日期的字符串。關(guān)于日期類的完整解釋,以及構(gòu)造函數(shù)的參數(shù)格式,參見規(guī)范節(jié)。 上一篇文章:MongoDB指南---2、MongoDB基礎(chǔ)知識(shí)-文檔、集合、數(shù)據(jù)庫、客戶端下一篇文章:MongoDB指南---4、MongoDB基礎(chǔ)知識(shí)-使用M...
摘要:例如,假設(shè)要?jiǎng)h除集合中所有為的人刪除數(shù)據(jù)是永久性的,不能撤銷,也不能恢復(fù)。刪除速度刪除文檔通常很快,但是如果要清空整個(gè)集合,那么使用直接刪除集合會(huì)更快然后在這個(gè)空集合上重建各項(xiàng)索引。上一篇文章指南基礎(chǔ)知識(shí)使用下一篇文章指南更新文檔 上一篇文章:MongoDB指南---4、MongoDB基礎(chǔ)知識(shí)-使用MongoDB Shell下一篇文章:MongoDB指南---6、更新文檔 本章會(huì)介紹...
摘要:例如,假設(shè)要?jiǎng)h除集合中所有為的人刪除數(shù)據(jù)是永久性的,不能撤銷,也不能恢復(fù)。刪除速度刪除文檔通常很快,但是如果要清空整個(gè)集合,那么使用直接刪除集合會(huì)更快然后在這個(gè)空集合上重建各項(xiàng)索引。上一篇文章指南基礎(chǔ)知識(shí)使用下一篇文章指南更新文檔 上一篇文章:MongoDB指南---4、MongoDB基礎(chǔ)知識(shí)-使用MongoDB Shell下一篇文章:MongoDB指南---6、更新文檔 本章會(huì)介紹...
摘要:上一篇文章指南更新文檔下一篇文章指南特定類型的查詢本章將詳細(xì)介紹查詢。查詢條件和就是全部的比較操作符,分別對應(yīng)和。如果查詢優(yōu)化器可以更高效地處理,那就選擇使用它。注意,查詢優(yōu)化器不會(huì)對進(jìn)行優(yōu)化,這與其他操作符不同。 上一篇文章:MongoDB指南---6、更新文檔下一篇文章:MongoDB指南---8、特定類型的查詢 本章將詳細(xì)介紹查詢。主要會(huì)涵蓋以下幾個(gè)方面: 使用find或者f...
摘要:上一篇文章指南更新文檔下一篇文章指南特定類型的查詢本章將詳細(xì)介紹查詢。查詢條件和就是全部的比較操作符,分別對應(yīng)和。如果查詢優(yōu)化器可以更高效地處理,那就選擇使用它。注意,查詢優(yōu)化器不會(huì)對進(jìn)行優(yōu)化,這與其他操作符不同。 上一篇文章:MongoDB指南---6、更新文檔下一篇文章:MongoDB指南---8、特定類型的查詢 本章將詳細(xì)介紹查詢。主要會(huì)涵蓋以下幾個(gè)方面: 使用find或者f...
閱讀 2193·2020-06-12 14:26
閱讀 2495·2019-08-29 16:41
閱讀 1893·2019-08-29 15:28
閱讀 2461·2019-08-26 13:43
閱讀 764·2019-08-26 13:37
閱讀 2783·2019-08-23 18:13
閱讀 2812·2019-08-23 15:31
閱讀 1026·2019-08-23 14:10