首页 文章

MongoDB:处理自动递增模型id 's instead of Mongo'的本机ObjectID

提问于
浏览
9

由于管理决策,我们将userId用于users集合,postId用于posts集合,topicId用于topic集合,而不是每个集合的'_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会找到带有userId:3和udpate的现有文档,所以必须有一些我做错了,因为它给了我重复的键错误?

3 回答

  • 6

    我不知道你使用的是哪个版本的MongoDB和Mongoose,但我无法用MongoDB 3.0和Mongoose 4.1.10重现你的问题 .

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

    "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();
        });
    
  • 0

    通常,默认值ObjectId对于_id更为理想 . 在这种情况下,您可以覆盖默认的_id,也可以拥有自己的id字段(例如,在您的情况下为userId) .

    使用单独的 counters collection 来跟踪最后使用的数字序列 . _id 字段包含序列名称, seq 字段包含序列的最后一个值 .

    插入 counters collection ,userid的初始值:

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

    创建一个接受序列名称的 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问题,请查看此链接Create an auto-increment sequence field中的 Optimistic Loop 块 .

    第二种方法是使用像mongodb-autoincrement这样的猫鼬中间件 .

    希望能帮助到你 .

  • 5

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

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

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

    因为将字段自动递增为ids是在mongodb环境中使用的一种不好的做法,特别是如果要使用分片=>所有插入都将出现在最新的分片上=> mongodb集群必须经常重新 balancer /重新分配数据周围 . (目前这不会出现在您的系统上,因为您仍然使用生成的_id字段)

    您当然可以在user_id字段上创建唯一索引:https://docs.mongodb.org/manual/core/index-unique/#index-type-unique

相关问题