1. 程式人生 > >mongodb遊標錯誤:com.mongodb.MongoCursorNotFoundException: Query failed with error code -5

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萬了。

從目前實戰的效果來看,昨天跑了一下午,今天又跑了一上午,都沒有報這個錯誤了。

引數地址: