mongodb遊標錯誤:com.mongodb.MongoCursorNotFoundException: Query failed with error code -5
環境
mongodb:3.4.15
java:1.7
場景
對使用者日誌表補加時間戳的欄位;
具體程式碼如下:
public static void main(String[] args) {
final MongoCollection<Document> useropRecord;
//連線資料庫 start
MongoCredential credential = MongoCredential.createCredential("gg_user_db_rw", "gg_user_db", "gg_user_db_rw.gogoal.com" .toCharArray());
ServerAddress serverAddress;
serverAddress = new ServerAddress("106.75.51.20", 35724);//35724
List<ServerAddress> addrs = new ArrayList<ServerAddress>();
addrs.add(serverAddress);
List<MongoCredential> credentials = new ArrayList<MongoCredential>();
credentials.add(credential);
@SuppressWarnings ("resource")
MongoClient mongoClient = new MongoClient(addrs, credentials);
System.out.println("Connect to database successfully");
//連線資料庫 end
MongoDatabase database = mongoClient.getDatabase("gg_user_db");
useropRecord = database.getCollection("userop_record" );//埋點表
Document match = new Document();
match.append("_tm", null);
Date stringToDate = DateUtil.stringToDate("2017-01-01", "yyyy-MM-dd");
match.append("date", new Document("$gte", stringToDate));
match.append("code", "S3_06");
useropRecord.find(match).forEach(new Block<Document>() {
int aa=3000;
@Override
public void apply(Document doc) {
Document project = new Document();
project.append("$set", new Document("_tm", new BSONTimestamp((int)(System.currentTimeMillis() / 1000), aa++)));
useropRecord.updateMany(new BasicDBObject("_id", doc.get("_id")), project);
if(aa >= 4000){
aa = 3000;
}
}
}
);
}
上面這段程式碼 執行著就會報如下錯誤:
Exception in thread "main" com.mongodb.MongoCursorNotFoundException: Query failed with error code -5 and error message 'Cursor 5741457193246430646 not found on server 106.75.51.20:35724' on server 106.75.51.20:35724
at com.mongodb.operation.QueryHelper.translateCommandException(QueryHelper.java:27)
at com.mongodb.operation.QueryBatchCursor.getMore(QueryBatchCursor.java:213)
at com.mongodb.operation.QueryBatchCursor.hasNext(QueryBatchCursor.java:103)
at com.mongodb.MongoBatchCursorAdapter.hasNext(MongoBatchCursorAdapter.java:46)
at com.mongodb.OperationIterable.forEach(OperationIterable.java:72)
at com.mongodb.FindIterableImpl.forEach(FindIterableImpl.java:166)
at test.MongodbTimestamp.main(MongodbTimestamp.java:54)
這段錯誤的原因呢,這裡引用網上的解釋:
你在用 db.collection.find() 的時候,它返回的不是所有的資料,而實際上是一個“cursor”。它的預設行為是:第一次向資料庫查詢 101 個文件,或 1 MB 的文件,取決於哪個條件先滿足;之後每次
cursor 中的文件用盡後,查詢 4 MB 的文件。另外,find() 的預設行為是返回一個 10 分鐘無操作後超時的 cursor。如果我一個 batch 的文件十分鐘內沒處理完,過後再處理完了,再用同一個 cursor id 向伺服器取下一個 batch,這時候
cursor id 當然已經過期了,這也就能解釋為啥我得到 cursor id 無效的錯誤了。Stack Overflow 上有人提出過解決方法,是在 find() 時傳入 timeout=False 來禁用 10 分鐘超時的保護措施。但是我覺得這是非常差的辦法,因為如果你迴圈時產生異常,甚至斷電或斷網,都會導致 MongoDB 伺服器資源永遠無法被釋放。而更好的辦法是(我也發在了 Stack Overflow 上),估計一個 batch 大小,讓 MongoDB 客戶端每次抓取的文件在 10 分鐘內能用完,這樣客戶端就不得不 10 分鐘內至少聯絡伺服器一次,保證cursor 不超時。
具體做法:useropRecord.find(match).batchSize(10000)
修改後的程式碼
public static void main(String[] args) {
final MongoCollection<Document> useropRecord;
//連線資料庫 start
MongoCredential credential = MongoCredential.createCredential("gg_user_db_rw", "gg_user_db", "gg_user_db_rw.gogoal.com".toCharArray());
ServerAddress serverAddress;
serverAddress = new ServerAddress("106.75.51.20", 35724);//35724
List<ServerAddress> addrs = new ArrayList<ServerAddress>();
addrs.add(serverAddress);
List<MongoCredential> credentials = new ArrayList<MongoCredential>();
credentials.add(credential);
@SuppressWarnings("resource")
MongoClient mongoClient = new MongoClient(addrs, credentials);
System.out.println("Connect to database successfully");
//連線資料庫 end
MongoDatabase database = mongoClient.getDatabase("gg_user_db");
useropRecord = database.getCollection("userop_record");//埋點表
Document match = new Document();
match.append("_tm", null);
Date stringToDate = DateUtil.stringToDate("2017-01-01", "yyyy-MM-dd");
match.append("date", new Document("$gte", stringToDate));
match.append("code", "S3_06");
useropRecord.find(match).batchSize(10000).forEach(new Block<Document>() {
int aa=3000;
@Override
public void apply(Document doc) {
Document project = new Document();
project.append("$set", new Document("_tm", new BSONTimestamp((int)(System.currentTimeMillis() / 1000), aa++)));
useropRecord.updateMany(new BasicDBObject("_id", doc.get("_id")), project);
if(aa >= 4000){
aa = 3000;
}
}
}
);
}
這裡我估計的一個大小是10000
,之前是我寫5萬
,依然會報錯,後來改3萬
,還是會報錯。
最後我測試看了下,大概10
分鐘 資料庫就更新1萬
條資料,所以改成1萬
了。
從目前實戰的效果來看,昨天跑了一下午,今天又跑了一上午,都沒有報這個錯誤了。
引數地址: