【问题标题】:MongoDB: handling auto-incrementing model id's instead of Mongo's native ObjectIDMongoDB:处理自动递增模型 id 而不是 Mongo 的本机 ObjectID
【发布时间】:2016-01-31 08:18:43
【问题描述】:

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

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

使用 Mongoose,我们有一个架构将 userId 限制为唯一值 - 但是在更新用户模型时,将 upsert 设置为 true,MongoDB 似乎只查看 ObjectId集合来查看是否存在相同的集合 - 它不检查是否已经存在具有相同 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 的现有文档并更新它,所以一定是我做错了什么,因为它给了我重复键错误?

【问题讨论】:

  • 我觉得奇怪的是,在这种情况下,它试图插入一条新记录,而一条新记录显然已经存在并且应该与所使用的查询匹配。
  • 是的,我也觉得很奇怪,我肯定做错了什么,但我不知道是什么
  • 我知道当我第一次迁移到 mongodb 时考虑做同样的事情时遇到了同样的问题,但我认为我从来没有解决过它,我最终找到了我链接的文章,并认为这条路线不适合我们。
  • 该变通方法有效,但想找出原始问题出现的原因

标签: node.js mongodb mongoose


【解决方案1】:

通常,默认值 ObjectId 更适合 _id。在这里,在这种情况下,您可以覆盖默认的 _id 或者您可以拥有自己的 id 字段(如您的情况下的 userId)。

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

插入counters集合,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这样的猫鼬中间件。

希望对您有所帮助。

【讨论】:

  • 你能告诉我这个 getNextSequence() 函数是你在 mongodb(procedure) 还是在你的服务器上创建的??
  • @Sudhansu 您可以在服务器级别创建它作为数据库实用程序函数。
【解决方案2】:

我不知道您使用的是哪个版本的 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();
    });

【讨论】:

    【解决方案3】:

    如果您的查询条件在 _id 字段上匹配,则按照文档(MongoDB 3.0)upsert:true 将不会插入不存在的文档。

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

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

    因为在 mongodb 环境中使用自动递增字段作为 id 是一种不好的做法,尤其是如果您想使用分片 => 你所有的插入都将发生在最新的分片上 => mongodb 集群将不得不经常重新平衡/重新分配数据。 (目前这不会在您的系统上发生,因为您仍然使用生成的 _id 字段)

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

    【讨论】:

    • 我不明白为什么使用数字增量设置用户 ID 对分片来说是个问题。我认为创建用户配置文件只发生一次,并且设置以及我们如何设置用户 ID 对于分片系统是完全未知的?
    猜你喜欢
    • 1970-01-01
    • 2023-03-24
    • 2020-07-19
    • 2017-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多