MongoDB:处理自动递增模型ID而不是Mongo的本地ObjectID

由于pipe理决策,我们将userId用于用户集合,postId用于posts集合,topicId用于主题集合,而不是每个集合的“_id”作为唯一标识符。

这导致一些问题开始 – 我遇到的问题之一是与upserts –

使用Mongoose,我们有一个限制userId为唯一值的架构 – 但是当在用户模型上进行更新时,upsert设置为true ,MongoDB似乎只查看集合的ObjectIds,以查看是否存在同一个 – 它不检查模型是否已经存在与相同的userId – 因此,Mongo做插入而不是更新。

让我用一些数据来说明这一点:

假设用户的集合有一个文档:

{ _id:'561b0fad638e99481ab6d84a' userId:3, name:'foo' } 

我们然后运行:

 User.update({userId:3},{"$set":{name:'bar'},{upsert:true},function(err,resp){ if(err){ // "errMessage": "insertDocument :: caused by :: 11000 E11000 duplicate key error index: app42153482.users.$userId_1 dup key: { : 3 }", } }); 

人们会认为,MongoDB会find与userId:3现有的文件并udpate它,所以一定有一些我做错了,因为它给了我重复的关键错误?

我不知道你正在使用哪个版本的MongoDB和Mongoose,但是我不能用MongoDB 3.0和Mongoose 4.1.10重现你的问题。

我为你做了一个样本,它将创build并保存一个新用户,更新(使用upsert)它,并通过一个upsert创build另一个。 尝试运行这个代码:

 "use strict"; var mongoose=require("mongoose"); var Schema = require('mongoose').Schema; var ObjectId = mongoose.Schema.Types.ObjectId; // Connect to test mongoose.connect("mongodb://localhost:27017/test"); // Lets create your schema var userSchema = new Schema({ _id: ObjectId, userId: {type: Number, unique: true }, name: String }); var User = mongoose.model("User", userSchema, "Users"); User.remove() // Let's prune our collection to start clean .then( function() { // Create our sample record var myUser = new User({ _id:'561b0fad638e99481ab6d84a', userId:3, name:'foo' }); return myUser.save(); }) .then( function() { // Now its time to update (upsert userId 3) return User.update({userId:3},{"$set":{name:'bar'}},{upsert:true}); }) .then( function() { // Now its time to insert (upsert userId 4) return User.update({userId:4},{"$set":{name:'bee'}},{upsert:true}); }) .then( function() { // Lets show what we have inserted return User.find().then(function(data) {console.log(data)}); }) .catch( function(err) { // Show errors if anything goes wrong console.error("ERROR", err); }) .then( function() { mongoose.disconnect(); }); 

通常,默认值ObjectId对于_id更为理想。 在这种情况下,您可以覆盖默认的_id,或者您可以拥有自己的id字段(就像userId一样)。

使用单独的计数器集合来跟踪使用的最后一个数字序列。 _id字段包含序列名称, seq字段包含序列的最后一个值。

计数器集合中插入用户标识的初始值:

 db.counters.insert( { _id: "userid", seq: 0 } ) 

创build一个接受序列名称的getNextSequence函数。 该函数使用findAndModify()方法自动递增seq值并返回此新值:

 function getNextSequence(name) { var ret = db.counters.findAndModify( { query: { _id: name }, update: { $inc: { seq: 1 } }, new: true } ); return ret.seq; } 

insert()中使用这个getNextSequence()函数。

 db.users.insert( { _id: getNextSequence("userid"), name: "Sarah C." } ) db.users.insert( { _id: getNextSequence("userid"), name: "Bob D." } ) 

这样,您可以在相同的计数器集合中保持尽可能多的序列。 对于upsert问题,请查看此链接中的乐观循环块创build一个自动递增序列字段 。

第二种方法是使用像mongodb-autoincrement这样的mongoose中间件。

希望能帮助到你。

在(MongoDB 3.0的)文档之后,如果查询条件与_id字段匹配,upsert:true将不会插入不存在的文档。

请参阅: https : //docs.mongodb.org/manual/reference/method/db.collection.update/#mongodb30-upsert-id

为什么你不使用用户的user_name作为唯一的ID?

因为在mongodb环境中自动递增字段是个不好的习惯,特别是如果你想使用sharding =>所有的插入都会在最新的shard =>上发生,mongodb集群将不得不经常重新平衡/重新分配数据周围。 (目前这不会发生在你的系统上,因为你仍然使用生成的_id域)

您也可以在user_id字段中创build唯一索引: https : //docs.mongodb.org/manual/core/index-unique/#index-type-unique