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