Mongodb upsert操作似乎不是原子,它会抛出DuplicateKeyException

时间:2015-10-26 12:48:42

标签: atomic

我有一个名为'msgdb'的mongodb数据库,以及一个名为'roster'的集合。 “名册”中的文件如下:

{
  "userId": "sn99933289",
  "rosterGroups": [
    {
      "groupId": "242326",
      "groupName": "buddy",
      "rosterItems": [

      ]
    }
  ],
  "version": NumberInt(1)
}

我将字段“_id”设置为分片键,如下所示:

db.runCommand({shardcollection:“msgdb.roster”,key:{_ id:“hashed”}})

调用upsert方法的Java代码:

import com.mongodb.BasicDBObject;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

    private void saveRoster(Roster roster) {
        BasicDBObject dbObject = new BasicDBObject();
        mongoTemplate.getConverter().write(roster, dbObject);
        dbObject.remove("version");
        dbObject.remove("_class");
        Update update = new Update();
        for (Map.Entry<String, Object> entry : dbObject.entrySet()) {
            update.set(entry.getKey(), entry.getValue());
        }
        update.inc("version", 1);
        Criteria criteria = Criteria.where("_id").is(roster.getUserId());
        Query query = new Query(criteria);
        mongoTemplate.upsert(query, update, "roster");
    }

我从应用程序日志中找到了DuplicateKeyException。似乎upsert操作不是原子的。更具体的异常堆栈跟踪如下。如何避免这种异常?任何帮助/提示都表示赞赏。

org.springframework.dao.DuplicateKeyException: Write failed with error code 11000 and error message 'E11000 duplicate key error collection: msgdb.roster index: _id_ dup key: { : "sn88332138" }'; nested exception is com.mongodb.DuplicateKeyException: Write failed with error code 11000 and error message 'E11000 duplicate key error collection: msgdb.roster index: _id_ dup key: { : "sn88332138" }'
at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:69) ~[spring-data-mongodb-1.7.1.RELEASE.jar:na]
at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:2011) ~[spring-data-mongodb-1.7.1.RELEASE.jar:na]
at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:463) ~[spring-data-mongodb-1.7.1.RELEASE.jar:na]
at org.springframework.data.mongodb.core.MongoTemplate.doUpdate(MongoTemplate.java:1086) ~[spring-data-mongodb-1.7.1.RELEASE.jar:na]
at org.springframework.data.mongodb.core.MongoTemplate.upsert(MongoTemplate.java:1052) ~[spring-data-mongodb-1.7.1.RELEASE.jar:na]
at com.suning.im.server.center.roster.RosterServiceImpl.saveRoster(RosterServiceImpl.java:235) ~[classes:na]
at com.suning.im.server.center.roster.RosterServiceImpl.getRoster(RosterServiceImpl.java:68) ~[classes:na]
at com.suning.im.server.center.roster.RosterServiceImpl.getRosterPresencesWithVirtual(RosterServiceImpl.java:251) ~[classes:na]
at sun.reflect.GeneratedMethodAccessor54.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.7.0_25]
at java.lang.reflect.Method.invoke(Method.java:606) ~[na:1.7.0_25]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) ~[na:1.7.0_25]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) ~[na:1.7.0_25]
at java.lang.Thread.run(Thread.java:724) ~[na:1.7.0_25]

1 个答案:

答案 0 :(得分:2)

Upsert不是原子的。这是由Mongo文档暗示的:https://docs.mongodb.org/v3.0/reference/method/db.collection.update/

  

为避免多次插入同一文档,只使用upsert:如果查询字段是唯一索引的,则为true。

所以我建议仅在索引为唯一的字段上使用upsert。如果查询语句中有多个字段,则可以使用复合索引或将这些字段散列在一起(例如,对文档的id)。幸运的是,在使用_id字段时,您正在使用唯一索引。

将upsert包装在发出更新请求的try catch中可以解决您的问题,因为您确定曾经在catch块中存在该文档。