Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/docker/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 当文档不在工作集中时,使用upsert和唯一索引原子性更新MongoDB_Java_Multithreading_Mongodb_Concurrency - Fatal编程技术网

Java 当文档不在工作集中时,使用upsert和唯一索引原子性更新MongoDB

Java 当文档不在工作集中时,使用upsert和唯一索引原子性更新MongoDB,java,multithreading,mongodb,concurrency,Java,Multithreading,Mongodb,Concurrency,总之,当文档不是工作集的一部分(不在常驻内存中)时,我们在对现有文档进行并发更新时遇到了这种奇怪的行为 更多详情: 给定一个具有唯一索引的集合,并且在给定的现有文档上运行upsert为true的并发更新(3个线程)时,1到2个线程会引发以下异常: Processing failed (Write failed with error code 11000 and error message 'insertDocument :: caused by :: 11000 E11000 duplicate

总之,当文档不是工作集的一部分(不在常驻内存中)时,我们在对现有文档进行并发更新时遇到了这种奇怪的行为

更多详情:

给定一个具有唯一索引的集合,并且在给定的现有文档上运行upsert为true的并发更新(3个线程)时,1到2个线程会引发以下异常:

Processing failed (Write failed with error code 11000 and error message 'insertDocument :: caused by :: 11000 E11000 duplicate key error index: db1.col1.$key_1  dup key: { : 1008 }'):
根据文档,我希望这三个更新都能成功,因为我试图更新的文档已经存在。相反,它看起来像是在尝试对少数或所有更新请求进行插入,但由于唯一索引,很少有请求失败

在文档上重复相同的并发更新不会引发任何异常。另外,对文档使用find()将其放入工作集,然后对该文档运行并发更新也会按预期运行。 此外,对相同的查询和设置使用findAndModify也不会有相同的问题

这是按预期工作还是我遗漏了什么

设置:

-mongodb java驱动程序3.0.1

-运行MongoDB版本“2.6.3”的3节点副本集

查询:

BasicDBObject query = new BasicDBObject();  
query.put("docId", 123L);
collection.update (query, object, true, false);
索引:

name: docId_1
unique: true
key: {"docId":1}
background: true
5月28日更新,包括重现该问题的示例代码。 按如下方式在本地运行MongoDB(请注意,测试将写入约4 GB的数据): ./mongodb-osx-x86_64-2.6.10/bin/mongod--dbpath/tmp/mongo 运行以下代码,重新启动数据库,注释掉“fillUpCollection(testMongoDB.col1,value,0300);”,然后再次运行代码。根据机器的不同,您可能需要调整一些数字才能看到异常

package test;

import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class TestMongoDB {
    public static final String DOC_ID = "docId";
    public static final String VALUE = "value";
    public static final String DB_NAME = "db1";
    public static final String UNIQUE = "unique";
    public static final String BACKGROUND = "background";
    private DBCollection col1;
    private DBCollection col2;

    private static DBCollection getCollection(Mongo mongo, String collectionName) {
        DBCollection col =  mongo.getDB(DB_NAME).getCollection(collectionName);
        BasicDBObject index = new BasicDBObject();
        index.append(DOC_ID, 1);
        DBObject indexOptions = new BasicDBObject();
        indexOptions.put(UNIQUE, true);
        indexOptions.put(BACKGROUND, true);
        col.createIndex(index, indexOptions);
        return col;
    }

    private static void storeDoc(String docId, DBObject doc, DBCollection dbCollection) throws IOException {
        BasicDBObject query = new BasicDBObject();
        query.put(DOC_ID, docId);
        dbCollection.update(query, doc, true, false);
        //dbCollection.findAndModify(query, null, null, false, doc, false, true);
    }

    public static void main(String[] args) throws Exception{
        final String value = new String(new char[1000000]).replace('\0', 'a');
        Mongo mongo = new MongoClient("localhost:27017");
        final TestMongoDB testMongoDB = new TestMongoDB();
        testMongoDB.col1 = getCollection(mongo, "col1");
        testMongoDB.col2 = getCollection(mongo, "col2");

        fillUpCollection(testMongoDB.col1, value, 0, 300);
        //restart Database, comment out previous line, and run again
        fillUpCollection(testMongoDB.col2, value, 0, 2000);
        updateExistingDocuments(testMongoDB, value);
    }

    private static void updateExistingDocuments(TestMongoDB testMongoDB, String value) {
        List<String> docIds = new ArrayList<String>();
        for(int i = 0; i < 10; i++) {
            docIds.add(new Random().nextInt(300) + "");
        }
        multiThreadUpdate(testMongoDB.col1, value, docIds);
    }


    private static void multiThreadUpdate(final DBCollection col, final String value, final List<String> docIds) {
        Runnable worker = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("Started Thread");
                    for(String id : docIds) {
                        storeDoc(id, getDbObject(value, id), col);
                    }
                } catch (Exception e) {
                    System.out.println(e);
                } finally {
                    System.out.println("Completed");
                }
            }
        };

        for(int i = 0; i < 8; i++) {
            new Thread(worker).start();
        }
    }

    private static DBObject getDbObject(String value, String docId) {
        final DBObject object2 = new BasicDBObject();
        object2.put(DOC_ID, docId);
        object2.put(VALUE, value);
        return object2;
    }

    private static void fillUpCollection(DBCollection col, String value, int from, int to) throws IOException {
        for(int i = from ; i <= to; i++) {
            storeDoc(i + "", getDbObject(value, i + ""), col);
        }
    }
}

您的查询太具体,即使文档已创建,也找不到文档,例如,不仅搜索唯一字段。然后,upsert尝试第二次创建它(另一个线程),但由于它实际存在而失败,但找不到它。有关更多详细信息,请参阅

从文档精简:为避免多次插入同一文档,仅当查询字段具有唯一索引时才使用upsert:true。 使用修改运算符(如$set)将查询文档包括到upsert文档中

如果你觉得这不适合你。请向我们提供有关索引的查询和一些信息

更新:

如果尝试从cli运行代码,将看到以下内容:

> db.upsert.ensureIndex({docid:1},{unique:true})
{
    "createdCollectionAutomatically" : true,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}
> db.upsert.update({"docid":123},{one:1,two:2},true,false)
WriteResult({
    "nMatched" : 0,
    "nUpserted" : 1,
    "nModified" : 0,
    "_id" : ObjectId("55637413ad907a45eec3a53a")
})
> db.upsert.find()
{ "_id" : ObjectId("55637413ad907a45eec3a53a"), "one" : 1, "two" : 2 }
> db.upsert.update({"docid":123},{one:1,two:2},true,false)
WriteResult({
    "nMatched" : 0,
    "nUpserted" : 0,
    "nModified" : 0,
    "writeError" : {
        "code" : 11000,
        "errmsg" : "insertDocument :: caused by :: 11000 E11000 duplicate key error index: test.upsert.$docid_1  dup key: { : null }"
    }
})
您有以下问题:

  • 您想更新文档,但找不到它。并且您的更新不包含修改运算符,因此您的docid字段将不会包含在新创建的文档中(或者更好地将其设置为null,并且在唯一索引中只能设置null一次)
  • 下次尝试更新文档时,由于上一步的原因,仍然找不到它。因此MongoDB尝试按照与前面相同的过程插入它,但再次失败。不允许第二个空值
只需将更新查询更改为此,即可修改文档/on upsert case,并将查询包含在其中:db.upsert.update({“docid”:123},{$set:{one:1,two:2}},true,false)


这似乎是MongoDB的一个已知问题,至少在版本2.6之前。他们建议的修复方法是让您的代码重试upsert on错误。

我觉得这不是我的情况,因为查询非常简单。我添加了查询和索引的外观。另外,添加了相同查询和索引上的findAndModify操作不会导致相同的异常。请查看我的更新答案,如果您在没有文档的情况下运行此操作,新创建的文档将不包含docid:db.upsert.update({“docid”:123},{one:1,two:2},true,false)只有带$set的才会有。谢谢更新。如果在第一次更新中插入的文档具有docId字段,那么第二次更新应该会找到该文档,这就是我们正在做的。没错,但只有在使用后面的查询(扩展为$set的查询)调用时才会出现这种情况。请您自己试一试,您会看到。当您执行$set时,它正在更新现有文档。如果没有$set操作符,它将执行替换。我们使用replace,更新的工作方式与下面的示例类似:db.col1.update({“docid”:123},{docid:123,one:1,two:2},true,false)。我们的问题是当索引或文档不在工作集中时这些更新的并发性。当我有时间的时候,我会做一个模拟,试图重现这个问题。我看不出有什么结果。您正试图保存两个文档,由于您没有分配一个,因此最终会得到不同的
\u id
。它们恰好包含相同的
docId
,由于索引的唯一约束,这是非法的。因此,第一个按预期插入,第二个不能,因为这将违反唯一约束。它是upsert参数设置为true的更新查询。我更新了“查询”部分以使其更加明确。请提供所有信息,包括您的查询,否则回答您的问题将变成一场假设游戏。我不太确定我还缺少哪些其他信息。请让我确切地知道哪些信息会有所帮助。这个问题正是我的问题。我可以补充一点,索引大小是2MB。共有53K个文档,文档大小在1KB到1MB之间。集合大小为15GB,内存为4GB。集群中还有许多其他集合、索引和数据库,但它们之间没有任何直接交互。我希望有人熟悉Mongo如何执行更新,以及是否存在可以解释此问题的缺陷?我添加了一些可用于重现此问题的代码
> db.upsert.ensureIndex({docid:1},{unique:true})
{
    "createdCollectionAutomatically" : true,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}
> db.upsert.update({"docid":123},{one:1,two:2},true,false)
WriteResult({
    "nMatched" : 0,
    "nUpserted" : 1,
    "nModified" : 0,
    "_id" : ObjectId("55637413ad907a45eec3a53a")
})
> db.upsert.find()
{ "_id" : ObjectId("55637413ad907a45eec3a53a"), "one" : 1, "two" : 2 }
> db.upsert.update({"docid":123},{one:1,two:2},true,false)
WriteResult({
    "nMatched" : 0,
    "nUpserted" : 0,
    "nModified" : 0,
    "writeError" : {
        "code" : 11000,
        "errmsg" : "insertDocument :: caused by :: 11000 E11000 duplicate key error index: test.upsert.$docid_1  dup key: { : null }"
    }
})
db.upsert.update({"docid":123},{$set:{one:1,two:2}},true,false)
WriteResult({
    "nMatched" : 0,
    "nUpserted" : 1,
    "nModified" : 0,
    "_id" : ObjectId("5562164f0f63858bf27345f3")
})
> db.upsert.find()
{ "_id" : ObjectId("5562164f0f63858bf27345f3"), "docid" : 123, "one" : 1, "two" : 2 }
> db.upsert.update({"docid":123},{$set:{one:1,two:2}},true,false)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })