Mongodb upsert 操作似乎不是原子的,它会抛出 DuplicateKeyException
Mongodb upsert operation seems not atomic which throws DuplicateKeyException
我有一个名为 'msgdb' 的 mongodb 数据库和一个名为 'roster' 的集合。 '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 操作不是原子的。更具体的异常堆栈跟踪如下。如何避免这种异常?任何 help/hint 表示赞赏。
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]
Upsert 不是原子的。 Mongo 文档暗示了这一点:https://docs.mongodb.org/v3.0/reference/method/db.collection.update/.
To avoid inserting the same document more than once, only use upsert: true if the query field is uniquely indexed.
所以我建议仅在索引为唯一的字段上使用 upsert。如果查询语句中有多个字段,则可以使用复合索引或将这些字段散列在一起(例如散列到文档的 id 中)。幸运的是,您在这里使用的是唯一索引,因为您正在使用 _id 字段。
将您的 upsert 包装在发出更新请求的 try catch 中应该可以解决您的问题,因为您确定一旦在 catch 块中文档就存在。
我有一个名为 'msgdb' 的 mongodb 数据库和一个名为 'roster' 的集合。 '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 操作不是原子的。更具体的异常堆栈跟踪如下。如何避免这种异常?任何 help/hint 表示赞赏。
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]
Upsert 不是原子的。 Mongo 文档暗示了这一点:https://docs.mongodb.org/v3.0/reference/method/db.collection.update/.
To avoid inserting the same document more than once, only use upsert: true if the query field is uniquely indexed.
所以我建议仅在索引为唯一的字段上使用 upsert。如果查询语句中有多个字段,则可以使用复合索引或将这些字段散列在一起(例如散列到文档的 id 中)。幸运的是,您在这里使用的是唯一索引,因为您正在使用 _id 字段。
将您的 upsert 包装在发出更新请求的 try catch 中应该可以解决您的问题,因为您确定一旦在 catch 块中文档就存在。