摘要:本質(zhì)上所有查詢的數(shù)據(jù)都是從游標(biāo)來的。的作用是從游標(biāo)中提取一批數(shù)據(jù),具體提取多少則是由決定。同時注意我們已經(jīng)有了一個游標(biāo)。為了便于理解,我們下面還是稱之為游標(biāo)超時。
前言
聊一聊一個最基本的問題,游標(biāo)的使用。可能你從來沒有注意過它,但其實它在MongoDB的使用中是普遍存在的,也存在一些常見的坑需要引起我們的注意。
在寫這個系列文章時,我會假設(shè)讀者已經(jīng)對MongoDB有了最基礎(chǔ)的了解,因此一些基本名詞和概念就不做過多的解釋,請自己查閱相關(guān)資料。
使用場景可能你以為你并沒有經(jīng)常在使用游標(biāo),但是其實只要在做查詢,幾乎時時刻刻都在用它。本質(zhì)上所有查詢的數(shù)據(jù)都是從游標(biāo)來的。你說你用toArray()?不存在的,它也是在遍歷游標(biāo)然后返回給你一個數(shù)組而已。正是因為這樣,就出現(xiàn)了第一個問題:除非你確定返回數(shù)據(jù)量有限,否則不要隨便toArray()。
這里說的toArray()包括:
shell中的toArray()。例如: var result = db.coll.find().toArray();
node中的toArray()。例如:var result = await db.collection("coll").find().toArray();
python中的list()。例如:result = list(db.coll.find());
Java中的toArray()。例如:DBCursor.toArray();
因為無論游標(biāo)里有多少數(shù)據(jù),toArray()都會給你挖出來放到內(nèi)存里,變成數(shù)組返回給你。慢不說,內(nèi)存也占用了很多。所以在可能的情況下,還是盡可能使用hasNext()/next()來得更好。
游標(biāo)主要來自兩個地方:
find
aggregation
注意二者返回的雖然都是“游標(biāo)”,但又是兩種不同的游標(biāo),使用上API也不完全相同,使用的時候請先查閱API(特別是使用NodeJS之類的動態(tài)語言的時候不要想當(dāng)然)。
batchSize與getmore說完從哪里來,下面就該說說怎么用的問題。
可能你已經(jīng)從什么地方看到過getmore,比如mongostat的結(jié)果中。getmore的作用是從游標(biāo)中提取一批數(shù)據(jù),具體提取多少則是由batchSize決定。
所以當(dāng)程序進(jìn)行查詢的時候,實際上在后臺發(fā)生的事情包括:
驅(qū)動在后臺獲取batchSize條數(shù)據(jù)并自己緩存起來;
每次程序調(diào)用游標(biāo)的next()方法時,從這些緩存中提取一條并返回;
當(dāng)batchSize條數(shù)據(jù)都返回完之后,驅(qū)動再次通過getmore獲取batchSize條數(shù)據(jù)。
我們可以通過shell來觀察這一過程:
先插入一批數(shù)據(jù):
use foo for(var i = 0; i < 1000; i++) { db.bar.insert({i: i}); }
強(qiáng)制日志記錄所有操作:
db.setProfilingLevel(0, 0)
跟蹤日志:
tail -f mongod.log
現(xiàn)在執(zhí)行一條find語句:
replset:PRIMARY> db.bar.find().batchSize(50);
2018-12-29T16:01:29.587+0800 I COMMAND [conn12] command test.bar appName: "MongoDB Shell" command: find { find: "bar", filter: {}, batchSize: 50.0, $clusterTime: { clusterTime: Timestamp(1546070474, 1), signature: { hash: BinData(0, 0000000000000000000000000000000000000000), keyId: 0 } }, $db: "test" } planSummary: COLLSCAN cursorid:77199395767 keysExamined:0 docsExamined:50 numYields:0 nreturned:50 reslen:2062 locks:{ Global: { acquireCount: { r: 1 } }, Database: { acquireCount: { r: 1 } }, Collection: { acquireCount: { r: 1 } } } protocol:op_msg 0ms
雖然我們在shell中只輸出了20條結(jié)果,但實際上我們已經(jīng)從這個游標(biāo)中獲取了50條數(shù)據(jù)(日志中的黑體部分)。所以當(dāng)我們繼續(xù)遍歷這個游標(biāo)時是暫時不需要再次從數(shù)據(jù)庫中取數(shù)據(jù)的。同時注意我們已經(jīng)有了一個游標(biāo)cursor:77199395767。
但當(dāng)我們第三次遍歷20條數(shù)據(jù)時,則會出現(xiàn)getmore日志:
replset:PRIMARY> it
2018-12-29T16:03:46.007+0800 I COMMAND [conn12] command test.bar appName: "MongoDB Shell" command: getMore { getMore: 77199395767, collection: "bar", batchSize: 50.0, $clusterTime: { clusterTime: Timestamp(1546070594, 1), signature: { hash: BinData(0, 0000000000000000000000000000000000000000), keyId: 0 } }, $db: "test" } originatingCommand: { find: "bar", filter: {}, batchSize: 50.0, $clusterTime: { clusterTime: Timestamp(1546070474, 1), signature: { hash: BinData(0, 0000000000000000000000000000000000000000), keyId: 0 } }, $db: "test" } planSummary: COLLSCAN cursorid:77199395767 keysExamined:0 docsExamined:50 numYields:0 nreturned:50 reslen:2061 locks:{ Global: { acquireCount: { r: 1 } }, Database: { acquireCount: { r: 1 } }, Collection: { acquireCount: { r: 1 } } } protocol:op_msg 0ms
2018-12-29T16:03:46.010+0800 I COMMAND [conn12] command admin.$cmd appName: "MongoDB Shell" command: replSetGetStatus { replSetGetStatus: 1.0, forShell: 1.0, $clusterTime: { clusterTime: Timestamp(1546070624, 1), signature: { hash: BinData(0, 0000000000000000000000000000000000000000), keyId: 0 } }, $db: "admin" } numYields:0 reslen:896 locks:{} protocol:op_msg 0ms
它通過同一個游標(biāo)再次提取了50條數(shù)據(jù)供使用。當(dāng)我們用完緩存中的數(shù)據(jù)之前都是不會再看到新的getmore指令的。
游標(biāo)超時上面已經(jīng)了解了游標(biāo)與驅(qū)動是如何配合工作的,那么游標(biāo)超時是怎么發(fā)生的呢?條件很簡單,2次getmore之間間隔了超過10分鐘,即一個游標(biāo)在服務(wù)端超過10分鐘無人訪問,則會被回收掉。這時候如果你再針對這個游標(biāo)進(jìn)行getmore,就會得到游標(biāo)不存在的錯誤(是的,超時的游標(biāo)在數(shù)據(jù)庫中是不存在的,你得到的錯誤不會是超時,而是游標(biāo)不存在。為了便于理解,我們下面還是稱之為“游標(biāo)超時”)。
那么假設(shè)你通過游標(biāo)讀取數(shù)據(jù)的時候是為了進(jìn)行一系列分析處理,那么下一次getmore在什么時候發(fā)生將取決于你的應(yīng)用在多長時間內(nèi)消耗完了當(dāng)前緩存中的數(shù)據(jù)。換句話說,你的應(yīng)用處理得越慢,下一次getmore發(fā)生的時間就越晚。很多驅(qū)動中batchSize的默認(rèn)值是1000,這也代表著你的應(yīng)用必須至少能夠在10分鐘內(nèi)處理1000條數(shù)據(jù),否則就會得到游標(biāo)超時錯誤。所以諸如每一條數(shù)據(jù)需要查詢其他數(shù)據(jù)庫1次,需要通過RESTful API到互聯(lián)網(wǎng)上獲取相關(guān)的數(shù)據(jù),或者需要進(jìn)行一系列復(fù)雜的運算,這樣的場景下,問題的關(guān)鍵其實不在于MongoDB怎么樣,而在于你的應(yīng)用到底能夠處理多快。
假設(shè)問題還是發(fā)生了,你的應(yīng)用遇到了游標(biāo)超時錯誤,怎么辦呢?你至少可以有以下一些選擇:
延長游標(biāo)超時時間,請參考cursorTimeoutMillis;
加速應(yīng)用的處理速度,處理得快了,下一次getmore自然就發(fā)生得更早;
不是那么直觀,但是減小batchSize也可以達(dá)到同樣的目的;
禁用超時時間(noCursorTimeout)——絕對不推薦使用。雖然可以達(dá)到目的,你也可以說我會在最后主動關(guān)閉游標(biāo)的,但事實上總會發(fā)生這樣那樣的意外,導(dǎo)致你最終沒有正確關(guān)閉游標(biāo),最后服務(wù)器上塞滿了游標(biāo)的情況也是很常見的。
例外情況上面已經(jīng)解釋過,在游標(biāo)超時的時候你得到的實際是“游標(biāo)不存在”錯誤,而不是超時。那么反過來是不是也成立呢,“游標(biāo)不存在”一定是超時了嗎?離散數(shù)學(xué)告訴我們,一個命題的逆命題不一定成立。事實上也是如此。“游標(biāo)不存在”的另一種可能性是有些用戶熱衷于在MongoDB前面加上負(fù)載均衡/自動故障恢復(fù)的軟/硬件。我們已經(jīng)知道游標(biāo)是存在于一臺服務(wù)器上的,如果你的負(fù)載均衡毫無原則地將請求轉(zhuǎn)發(fā)到任意服務(wù)器上,getmore同時會因為找不到游標(biāo)而出現(xiàn)“游標(biāo)不存在”的錯誤。
事實上MongoDB和其驅(qū)動本身就已經(jīng)能夠完成高可用和負(fù)載均衡,并不需要額外畫蛇添足。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/19441.html
摘要:前言數(shù)據(jù)更新,中的,對任何數(shù)據(jù)庫而言都是最基本的操作。你并不能保證數(shù)據(jù)在被你讀出來到寫回去期間是否有別人已經(jīng)改了數(shù)據(jù)庫中的記錄,這就是第一個風(fēng)險,操作存在潛在的可能性會覆蓋掉別人更新過的數(shù)據(jù)。 前言 數(shù)據(jù)更新,CRUD中的U,對任何數(shù)據(jù)庫而言都是最基本的操作。看似簡單的更新操作中會藏著哪些坑?今天聊一聊這個話題。 在寫這個系列文章時,我會假設(shè)讀者已經(jīng)對MongoDB有了最基礎(chǔ)的了解,因...
摘要:注意記住的作用始終是把集群中具有投票權(quán)的節(jié)點總數(shù)湊成奇數(shù)用,防止腦裂。其代表的意義是集群中必須有大多數(shù)節(jié)點收到并確認(rèn)了一個寫操作,這個寫操作才算成功。無論源或者目標(biāo)片中不能夠滿足大多數(shù)時,遷移都會失敗。在有可能的情況下,應(yīng)盡量使用代替。 前言 在技術(shù)社區(qū)混了這么長時間,因為一些常見的技術(shù)問題反復(fù)被問到,總是想寫寫文章把它們講清楚。無奈很多時候看似基礎(chǔ)的技術(shù)問題背后都隱藏著很深的原因,想...
摘要:簡述是中操作的模塊,其使用方法和幾乎相同。但目前支持而后者不支持版本。因此要避免這種情況需使用提供的參數(shù)化查詢。使用存儲過程動態(tài)執(zhí)行防注入使用存儲過程自動提供防注入,動態(tài)傳入到存儲過程執(zhí)行語句。 簡述 pymsql是Python中操作MySQL的模塊,其使用方法和MySQLdb幾乎相同。但目前pymysql支持python3.x而后者不支持3.x版本。本文測試python版本:3.5....
閱讀 2331·2021-11-24 10:27
閱讀 3576·2019-08-30 15:55
閱讀 3341·2019-08-30 15:53
閱讀 2342·2019-08-29 17:27
閱讀 1428·2019-08-26 13:47
閱讀 3547·2019-08-26 10:28
閱讀 913·2019-08-23 15:59
閱讀 2850·2019-08-23 15:19