摘要:例如在中,可以用產(chǎn)生一個的隨機數(shù)這樣,想要從集合中查找一個隨機文檔,只要計算一個隨機數(shù)并將其作為查詢條件就好了,完全不用偶爾也會遇到產(chǎn)生的隨機數(shù)比集合中所有隨機值都大的情況,這時就沒有結果返回了。指定本次查詢中掃描文檔數(shù)量的上限。
上一篇文章:MongoDB指南---8、特定類型的查詢
下一篇文章:MongoDB指南---10、索引、復合索引 簡介
數(shù)據(jù)庫使用游標返回find的執(zhí)行結果。客戶端對游標的實現(xiàn)通常能夠對最終結果進行有效的控制。可以限制結果的數(shù)量,略過部分結果,根據(jù)任意鍵按任意順序的組合對結果進行各種排序,或者是執(zhí)行其他一些強大的操作。
要想從shell中創(chuàng)建一個游標,首先要對集合填充一些文檔,然后對其執(zhí)行查詢,并將結果分配給一個局部變量(用var聲明的變量就是局部變量)。這里,先創(chuàng)建一個簡單的集合,而后做個查詢,并用cursor變量保存結果:
> for(i=0; i<100; i++) { ... db.collection.insert({x : i}); ... } > var cursor = db.collection.find();
這么做的好處是可以一次查看一條結果。如果將結果放在全局變量或者就沒有放在變量中,MongoDB shell會自動迭代,自動顯示最開始的若干文檔。也就是在這之前我們看到的種種例子,一般大家只想通過shell看看集合里面有什么,而不是想在其中實際運行程序,這樣設計也就很合適。
要迭代結果,可以使用游標的next方法。也可以使用hasNext來查看游標中是否還有其他結果。典型的結果遍歷如下所示:
> while (cursor.hasNext()) { ... obj = cursor.next(); ... // do stuff ... }
cursor.hasNext()檢查是否有后續(xù)結果存在,然后用cursor.next()獲得它。
游標類還實現(xiàn)了JavaScript的迭代器接口,所以可以在forEach循環(huán)中使用:
> var cursor = db.people.find(); > cursor.forEach(function(x) { ... print(x.name); ... }); adam matt zak
調用find時,shell并不立即查詢數(shù)據(jù)庫,而是等待真正開始要求獲得結果時才發(fā)送查詢,這樣在執(zhí)行之前可以給查詢附加額外的選項。幾乎游標對象的每個方法都返回游標本身,這樣就可以按任意順序組成方法鏈。例如,下面幾種表達是等價的:
> var cursor = db.foo.find().sort({"x" : 1}).limit(1).skip(10); > var cursor = db.foo.find().limit(1).sort({"x" : 1}).skip(10); > var cursor = db.foo.find().skip(10).limit(1).sort({"x" : 1});
此時,查詢還沒有真正執(zhí)行,所有這些函數(shù)都只是構造查詢。現(xiàn)在,假設我們執(zhí)行如下操作:
> cursor.hasNext()
這時,查詢被發(fā)往服務器。shell立刻獲取前100個結果或者前4 MB數(shù)據(jù)(兩者之中較小者),這樣下次調用next或者hasNext時就不必再次連接服務器取結果了。客戶端用光了第一組結果,shell會再一次聯(lián)系數(shù)據(jù)庫,使用getMore請求提取更多的結果。getMore請求包含一個查詢標識符,向數(shù)據(jù)庫詢問是否還有更多的結果,如果有,則返回下一批結果。這個過程會一直持續(xù)到游標耗盡或者結果全部返回。
4.5.1 limit、skip和sort最常用的查詢選項就是限制返回結果的數(shù)量、忽略一定數(shù)量的結果以及排序。所有這些選項一定要在查詢被發(fā)送到服務器之前指定。
要限制結果數(shù)量,可在find后使用limit函數(shù)。例如,只返回3個結果,可以這樣:
> db.c.find().limit(3)
要是匹配的結果不到3個,則返回匹配數(shù)量的結果。limit指定的是上限,而非下限。
skip與limit類似:
> db.c.find().skip(3)
上面的操作會略過前三個匹配的文檔,然后返回余下的文檔。如果集合里面能匹配的文檔少于3個,則不會返回任何文檔。
sort接受一個對象作為參數(shù),這個對象是一組鍵/值對,鍵對應文檔的鍵名,值代表排序的方向。排序方向可以是1(升序)或者-1(降序)。如果指定了多個鍵,則按照這些鍵被指定的順序逐個排序。例如,要按照"username"升序及"age"降序排序,可以這樣寫:
> db.c.find().sort({username : 1, age : -1})
這3個方法可以組合使用。這對于分頁非常有用。例如,你有個在線商店,有人想搜索mp3。若是想每頁返回50個結果,而且按照價格從高到低排序,可以這樣寫:
> db.stock.find({"desc" : "mp3"}).limit(50).sort({"price" : -1})
點擊“下一頁”可以看到更多的結果,通過skip也可以非常簡單地實現(xiàn),只需要略過前50個結果就好了(已經(jīng)在第一頁顯示了):
> db.stock.find({"desc" : "mp3"}).limit(50).skip(50).sort({"price" : -1})
然而,略過過多的結果會導致性能問題,下一小節(jié)會講述如何避免略過大量結果。
比較順序MongoDB處理不同類型的數(shù)據(jù)是有一定順序的。有時一個鍵的值可能是多種類型的,例如,整型和布爾型,或者字符串和null。如果對這種混合類型的鍵排序,其排序順序是預先定義好的。優(yōu)先級從小到大,其順序如下:
最小值;
null;
數(shù)字(整型、長整型、雙精度);
字符串;
對象/文檔;
數(shù)組;
二進制數(shù)據(jù);
對象ID;
布爾型;
日期型;
時間戳;
正則表達式;
最大值 。
4.5.2 避免使用skip略過大量結果用skip略過少量的文檔還是不錯的。但是要是數(shù)量非常多的話,skip就會變得很慢,因為要先找到需要被略過的數(shù)據(jù),然后再拋棄這些數(shù)據(jù)。大多數(shù)數(shù)據(jù)庫都會在索引中保存更多的元數(shù)據(jù),用于處理skip,但是MongoDB目前還不支持,所以要盡量避免略過太多的數(shù)據(jù)。通常可以利用上次的結果來計算下一次查詢條件。
1. 不用skip對結果分頁最簡單的分頁方法就是用limit返回結果的第一頁,然后將每個后續(xù)頁面作為相對于開始的偏移量返回。
> // 不要這么用:略過的數(shù)據(jù)比較多時,速度會變得很慢 > var page1 = db.foo.find(criteria).limit(100) > var page2 = db.foo.find(criteria).skip(100).limit(100) > var page3 = db.foo.find(criteria).skip(200).limit(100) ...
然而,一般來講可以找到一種方法在不使用skip的情況下實現(xiàn)分頁,這取決于查詢本身。例如,要按照"date"降序顯示文檔列表。可以用如下方式獲取結果的第一頁:
> var page1 = db.foo.find().sort({"date" : -1}).limit(100)
然后,可以利用最后一個文檔中"date"的值作為查詢條件,來獲取下一頁:
var latest = null; // 顯示第一頁 while (page1.hasNext()) { latest = page1.next(); display(latest); } // 獲取下一頁 var page2 = db.foo.find({"date" : {"$gt" : latest.date}}); page2.sort({"date" : -1}).limit(100); 這樣查詢中就沒有skip了。2. 隨機選取文檔
從集合里面隨機挑選一個文檔算是個常見問題。最笨的(也很慢的)做法就是先計算文檔總數(shù),然后選擇一個從0到文檔數(shù)量之間的隨機數(shù),利用find做一次查詢,略過這個隨機數(shù)那么多的文檔,這個隨機數(shù)的取值范圍為0到集合中文檔的總數(shù):
> // 不要這么用 > var total = db.foo.count() > var random = Math.floor(Math.random()*total) > db.foo.find().skip(random).limit(1)
這種選取隨機文檔的做法效率太低:首先得計算總數(shù)(要是有查詢條件就會很費時),然后用skip略過大量結果也會非常耗時。
略微動動腦筋,從集合里面查找一個隨機元素還是有好得多的辦法的。秘訣就是在插入文檔時給每個文檔都添加一個額外的隨機鍵。例如在shell中,可以用Math.random()(產(chǎn)生一個0~1的隨機數(shù)):
> db.people.insert({"name" : "joe", "random" : Math.random()}) > db.people.insert({"name" : "john", "random" : Math.random()}) > db.people.insert({"name" : "jim", "random" : Math.random()})
這樣,想要從集合中查找一個隨機文檔,只要計算一個隨機數(shù)并將其作為查詢條件就好了,完全不用skip:
> var random = Math.random() > result = db.foo.findOne({"random" : {"$gt" : random}})
偶爾也會遇到產(chǎn)生的隨機數(shù)比集合中所有隨機值都大的情況,這時就沒有結果返回了。遇到這種情況,那就將條件操作符換一個方向:
> if (result == null) { ... result = db.foo.findOne({"random" : {"$lt" : random}}) ... }
要是集合里面本就沒有文檔,則會返回null,這說得通。
這種技巧還可以和其他各種復雜的查詢一同使用,僅需要確保有包含隨機鍵的索引即可。例如,想在加州隨機找一個水暖工,可以對"profession"、"state"和"random"建立索引:
> db.people.ensureIndex({"profession" : 1, "state" : 1, "random" : 1})
這樣就能很快得出一個隨機結果(關于索引,詳見第5章)。
4.5.3 高級查詢選項有兩種類型的查詢:簡單查詢(plain query)和封裝查詢(wrapped query)。簡單查詢就像下面這樣:
> var cursor = db.foo.find({"foo" : "bar"})
有一些選項可以用于對查詢進行“封裝”。例如,假設我們執(zhí)行一個排序:
> var cursor = db.foo.find({"foo" : "bar"}).sort({"x" : 1})
實際情況不是將{"foo" : "bar"}作為查詢直接發(fā)送給數(shù)據(jù)庫,而是先將查詢封裝在一個更大的文檔中。shell會把查詢從{"foo" : "bar"}轉換成{"$query" : {"foo" : "bar"},"$orderby" : {"x" : 1}}。
絕大多數(shù)驅動程序都提供了輔助函數(shù),用于向查詢中添加各種選項。下面列舉了其他一些有用的選項。
$maxscan : integer
指定本次查詢中掃描文檔數(shù)量的上限。
> db.foo.find(criteria)._addSpecial("$maxscan", 20)
如果不希望查詢耗時太多,也不確定集合中到底有多少文檔需要掃描,那么可以使用這個選項。這樣就會將查詢結果限定為與被掃描的集合部分相匹配的文檔。這種方式的一個壞處是,某些你希望得到的文檔沒有掃描到。
$min : document
查詢的開始條件。在這樣的查詢中,文檔必須與索引的鍵完全匹配。查詢中會強制使用給定的索引。
在內部使用時,通常應該使用"$gt"代替"$min"。可以使用"$min"強制指定一次索引掃描的下邊界,這在復雜查詢中非常有用。
$max : document
查詢的結束條件。在這樣的查詢中,文檔必須與索引的鍵完全匹配。查詢中會強制使用給定的索引。
在內部使用時,通常應該使用"$lg"而不是"$max"。可以使用"$max"強制指定一次索引掃描的上邊界,這在復雜查詢中非常有用。
$showDiskLoc : true
在查詢結果中添加一個"$diskLoc"字段,用于顯示該條結果在磁盤上的位置。例如:
> db.foo.find()._addSpecial("$showDiskLoc",true) { "_id" : 0, "$diskLoc" : { "file" : 2, "offset" : 154812592 } } { "_id" : 1, "$diskLoc" : { "file" : 2, "offset" : 154812628 } }
文件號碼顯示了這個文檔所在的文件。如果這里使用的是test數(shù)據(jù)庫,那么這個文檔就在test.2文件中。第二個字段顯示的是該文檔在文件中的偏移量。
4.5.4 獲取一致結果數(shù)據(jù)處理通常的做法就是先把數(shù)據(jù)從MongoDB中取出來,然后做一些變換,最后再存回去:
cursor = db.foo.find(); while (cursor.hasNext()) { var doc = cursor.next(); doc = process(doc); db.foo.save(doc); }
結果比較少,這樣是沒問題的,但是如果結果集比較大,MongoDB可能會多次返回同一個文檔。為什么呢?想象一下文檔究竟是如何存儲的吧。可以將集合看做一個文檔列表,如圖4-1所示。雪花代表文檔,因為每一個文檔都是美麗且唯一的。
圖4-1 待查詢的集合
這樣,進行查找時,從集合的開頭返回結果,游標不斷向右移動。程序獲取前100個文檔并處理。將這些文檔保存回數(shù)據(jù)庫時,如果文檔體積增加了,而預留空間不足,如圖4-2所示,這時就需要對體積增大后的文檔進行移動。通常會將它們挪至集合的末尾處(如圖4-3所示)。
圖4-2 體積變大的文檔,可能無法保存回原先的位置
圖4-3 MongoDB會為更新后無法放回原位置的文檔重新分配存儲空間
現(xiàn)在,程序繼續(xù)獲取大量的文檔,如此往復。當游標移動到集合末尾時,就會返回因體積太大無法放回原位置而被移動到集合末尾的文檔,如圖4-4所示。
圖4-4 游標可能會返回那些由于體積變大而被移動到集合末尾的文檔
應對這個問題的方法就是對查詢進行快照(snapshot)。如果使用了這個選項,查詢就在"_id"索引上遍歷執(zhí)行,這樣可以保證每個文檔只被返回一次。例如,將db.foo.find()改為:
> db.foo.find().snapshot()
快照會使查詢變慢,所以應該只在必要時使用快照。例如,mongodump(用于備份,第22章會介紹)默認在快照上使用查詢。
所有返回單批結果的查詢都被有效地進行了快照。當游標正在等待獲取下一批結果時,如果集合發(fā)生了變化,數(shù)據(jù)才可能出現(xiàn)不一致。
看待游標有兩種角度:客戶端的游標以及客戶端游標表示的數(shù)據(jù)庫游標。前面討論的都是客戶端的游標,接下來簡要看看服務器端發(fā)生了什么。
在服務器端,游標消耗內存和其他資源。游標遍歷盡了結果以后,或者客戶端發(fā)來消息要求終止,數(shù)據(jù)庫將會釋放這些資源。釋放的資源可以被數(shù)據(jù)庫另作他用,這是非常有益的,所以要盡量保證盡快釋放游標(在合理的前提下)。
還有一些情況導致游標終止(隨后被清理)。首先,游標完成匹配結果的迭代時,它會清除自身。另外,如果客戶端的游標已經(jīng)不在作用域內了,驅動程序會向服務器發(fā)送一條特別的消息,讓其銷毀游標。最后,即便用戶沒有迭代完所有結果,并且游標也還在作用域中,如果一個游標在10分鐘內沒有使用的話,數(shù)據(jù)庫游標也會自動銷毀。這樣的話,如果客戶端崩潰或者出錯,MongoDB就不需要維護這上千個被打開卻不再使用的游標。
這種“超時銷毀”的行為是我們希望的:極少有應用程序希望用戶花費數(shù)分鐘坐在那里等待結果。然而,有時的確希望游標持續(xù)的時間長一些。若是如此的話,多數(shù)驅動程序都實現(xiàn)了一個叫immortal的函數(shù),或者類似的機制,來告知數(shù)據(jù)庫不要讓游標超時。如果關閉了游標的超時時間,則一定要迭代完所有結果,或者主動將其銷毀,以確保游標被關閉。否則它會一直在數(shù)據(jù)庫中消耗服務器資源。
有一種非常特殊的查詢類型叫作數(shù)據(jù)庫命令(database command)。前面已經(jīng)介紹過文檔的創(chuàng)建、更新、刪除以及查詢。這些都是數(shù)據(jù)庫命令的使用范疇,包括管理性的任務(比如關閉服務器和克隆數(shù)據(jù)庫)、統(tǒng)計集合內的文檔數(shù)量以及執(zhí)行聚合等。
本節(jié)主要講述數(shù)據(jù)庫命令,在數(shù)據(jù)操作、管理以及監(jiān)控中,數(shù)據(jù)庫命令都是非常有用的。例如,刪除集合是使用"drop"數(shù)據(jù)庫命令完成的:
> db.runCommand({"drop" : "test"}); { "nIndexesWas" : 1, "msg" : "indexes dropped for collection", "ns" : "test.test", "ok" : true }
也許你對shell輔助函數(shù)比較熟悉,這些輔助函數(shù)封裝數(shù)據(jù)庫命令,并提供更加簡單的接口:
> db.test.drop()
通常,只使用shell輔助函數(shù)就可以了,但是了解它們底層的命令很有幫助。尤其是當使用舊版本的shell連接到新版本的數(shù)據(jù)庫上時,這個shell可能不支持新版數(shù)據(jù)庫的一些命令,這時候就不得不直接使用runCommand()。
在前面的章節(jié)中已經(jīng)看到過一些命令了,比如,第3章使用getLastError來查看更新操作影響到的文檔數(shù)量:
> db.count.update({x : 1}, {$inc : {x : 1}}, false, true) > db.runCommand({getLastError : 1}) { "err" : null, "updatedExisting" : true, "n" : 5, "ok" : true }
本節(jié)會更深入地介紹數(shù)據(jù)庫命令,一起來看看這些數(shù)據(jù)庫命令到底是什么,到底是怎么實現(xiàn)的。本節(jié)也會介紹MongoDB提供的一些非常有用的命令。在shell中運行db.listCommands()可以看到所有的數(shù)據(jù)庫命令。
數(shù)據(jù)庫命令工作原理數(shù)據(jù)庫命令總會返回一個包含"ok"鍵的文檔。如果"ok"的值是1,說明命令執(zhí)行成功了;如果值是0,說明由于一些原因,命令執(zhí)行失敗。
如果"ok"的值是0,那么命令的返回文檔中就會有一個額外的鍵"errmsg"。它的值是一個字符串,用于描述命令的失敗原因。例如,如果試著在上一節(jié)已經(jīng)刪除的集合上再次執(zhí)行drop命令:
> db.runCommand({"drop" : "test"}); { "errmsg" : "ns not found", "ok" : false }
MongoDB中的命令被實現(xiàn)為一種特殊類型的查詢,這些特殊的查詢會在$cmd集合上執(zhí)行。runCommand只是接受一個命令文檔,并且執(zhí)行與這個命令文檔等價的查詢。于是,drop命令會被轉換為如下代碼:
db.$cmd.findOne({"drop" : "test"});
當MongoDB服務器得到一個在$cmd集合上的查詢時,不會對這個查詢進行通常的查詢處理,而是會使用特殊的邏輯對其進行處理。幾乎所有的MongoDB驅動程序都會提供一個類似runCommand的輔助函數(shù),用于執(zhí)行命令,而且命令總是能夠以簡單查詢的方式執(zhí)行。
有些命令需要有管理員權限,而且要在admin數(shù)據(jù)庫上才能執(zhí)行。如果在其他數(shù)據(jù)庫上執(zhí)行這樣的命令,就會得到一個"access denied"(訪問被拒絕)錯誤。如果當前位于其他的數(shù)據(jù)庫,但是需要執(zhí)行一個管理員命令,可以使用adminCommand而不是runCommand:
> use temp switched to db temp > db.runCommand({shutdown:1}) { "errmsg" : "access denied; use admin db", "ok" : 0 } > db.adminCommand({"shutdown" : 1})
MongoDB中,數(shù)據(jù)庫命令是少數(shù)與字段順序相關的地方之一:命令名稱必須是命令中的第一個字段。因此, {"getLastError" : 1, "w" : 2}是有效的命令,而{"w" : 2, "getLastError" : 1}不是。
上一篇文章:MongoDB指南---8、特定類型的查詢
下一篇文章:MongoDB指南---10、索引、復合索引 簡介
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/19583.html
摘要:例如在中,可以用產(chǎn)生一個的隨機數(shù)這樣,想要從集合中查找一個隨機文檔,只要計算一個隨機數(shù)并將其作為查詢條件就好了,完全不用偶爾也會遇到產(chǎn)生的隨機數(shù)比集合中所有隨機值都大的情況,這時就沒有結果返回了。指定本次查詢中掃描文檔數(shù)量的上限。 上一篇文章:MongoDB指南---8、特定類型的查詢下一篇文章:MongoDB指南---10、索引、復合索引 簡介 數(shù)據(jù)庫使用游標返回find的執(zhí)行結果...
摘要:固定集合不能被分片。為固定集合指定文檔數(shù)量限制時,必須同時指定固定集合的大小。沒有索引的集合默認情況下,每個集合都有一個索引。 上一篇文章:MongoDB指南---13、索引類型、索引管理下一篇文章:MongoDB指南---15、特殊的索引和集合:地理空間索引、使用GridFS存儲文件 本章介紹MongoDB中一些特殊的集合和索引類型,包括: 用于類隊列數(shù)據(jù)的固定集合(capped...
摘要:固定集合不能被分片。為固定集合指定文檔數(shù)量限制時,必須同時指定固定集合的大小。沒有索引的集合默認情況下,每個集合都有一個索引。 上一篇文章:MongoDB指南---13、索引類型、索引管理下一篇文章:MongoDB指南---15、特殊的索引和集合:地理空間索引、使用GridFS存儲文件 本章介紹MongoDB中一些特殊的集合和索引類型,包括: 用于類隊列數(shù)據(jù)的固定集合(capped...
摘要:概述是的交互式接口你可以使用查詢和更新數(shù)據(jù)以及執(zhí)行管理操作是發(fā)行版的一個組件一旦你已經(jīng)安裝并且啟動了連接到你運行的實例在手冊的大部分示例都是使用然而許多驅動程序為提供了類似的接口啟動重要在嘗試運行之前確保正在運行啟動并使用默認端口連接到本地 概述 mongo shell 是 MongoDB的交互式 JavaScript 接口. 你可以使用 mongo shell 查詢和更新數(shù)據(jù)以及執(zhí)行...
摘要:可以通過來強制使用某個特定的索引,再次執(zhí)行這個查詢,但是這次使用,作為索引。 上一篇文章:MongoDB指南---9、游標與數(shù)據(jù)庫命令下一篇文章:MongoDB指南---11、使用復合索引、$操作符如何使用索引、索引對象和數(shù)組、索引基數(shù) 本章介紹MongoDB的索引,索引可以用來優(yōu)化查詢,而且在某些特定類型的查詢中,索引是必不可少的。 什么是索引?為什么要用索引? 如何選擇需要建立...
閱讀 3513·2021-11-17 17:01
閱讀 3919·2021-11-08 13:12
閱讀 2477·2021-10-08 10:04
閱讀 687·2021-09-29 09:35
閱讀 1418·2021-09-26 10:12
閱讀 2021·2021-09-07 09:58
閱讀 1953·2019-08-30 15:55
閱讀 2134·2019-08-30 13:14