一尘不染

猫鼬重复键错误与upsert

node.js

我有重复密钥的问题。很长一段时间找不到答案。请帮助我解决此问题或解释为什么我得到重复的密钥错误。

Trace: { [MongoError: E11000 duplicate key error collection: project.monitor index: _id_ dup key: { : 24392490 }]
name: 'MongoError',
message: 'E11000 duplicate key error collection: project.monitor index: _id_ dup key: { : 24392490 }',
driver: true,
index: 0,
code: 11000,
errmsg: 'E11000 duplicate key error collection: project.monitor index: _id_ dup key: { : 24392490 }' }
at /home/project/app/lib/monitor.js:67:12
at callback (/home/project/app/node_modules/mongoose/lib/query.js:2029:9)
at Immediate.<anonymous> (/home/project/app/node_modules/kareem/index.js:160:11)
at Immediate._onImmediate (/home/project/app/node_modules/mquery/lib/utils.js:137:16)
at processImmediate [as _immediateCallback] (timers.js:368:17)

但是在监视器中,我使用upsert,所以为什么会出现重复错误?

monitor.js:62-70

监控架构

var monitorSchema = db.Schema({
   _id      : {type: Number, default: utils.minute},
   maxTicks : {type: Number, default: 0},
   ticks    : {type: Number, default: 0},
   memory   : {type: Number, default: 0},
   cpu      : {type: Number, default: 0},
   reboot   : {type: Number, default: 0},
streams  : db.Schema.Types.Mixed
}, {
collection: 'monitor',
strict: false
});

指数

monitorSchema.index({_id: -1});
Monitor = db.model('Monitor', monitorSchema);

并增加财产

exports.increase = function (property, incr) {
    var update = {};
    update[property] = utils.parseRound(incr) || 1;
    Monitor.update({_id: utils.minute()}, {$inc: update}, {upsert: true}, function (err) {
        if (err) {
            console.trace(err);
        }
    });
};

utils.js

exports.minute = function () {
    return Math.round(Date.now() / 60000);
};

exports.parseRound = function (num, round) {
    if (isNaN(num)) return 0;
    return Number(parseFloat(Number(num)).toFixed(round));
};

阅读 191

收藏
2020-07-07

共1个答案

一尘不染

导致插入文档的ups不是完全原子的操作。将ups视为执行以下离散步骤:

  1. 查询标识的文档以进行增补。
  2. 如果文档存在,则自动更新现有文档。
  3. 否则(该文档不存在),自动插入一个包含查询字段和更新的新文档。

因此,第2步和第3步都是原子操作,但是在第1步之后可能会发生另一个upsert,因此您的代码需要检查重复的键错误,然后再次尝试upsert。到那时,您知道_id存在的文档,因此它将始终成功。

例如:

var minute = utils.minute();
Monitor.update({ _id: minute }, { $inc: update }, { upsert: true }, function(err) {
    if (err) {
        if (err.code === 11000) {
            // Another upsert occurred during the upsert, try again. You could omit the
            // upsert option here if you don't ever delete docs while this is running.
            Monitor.update({ _id: minute }, { $inc: update }, { upsert: true },
                function(err) {
                    if (err) {
                        console.trace(err);
                    }
                });
        }
        else {
            console.trace(err);
        }
    }
});

有关相关文档,请参见此处

您可能仍然想知道,如果插入是原子的,为什么会发生这种情况,但这意味着插入的文档在完全写入之前不会进行任何更新,而不是不会_id出现具有相同文档的其他插入。

另外,您不需要手动创建索引,_id因为所有MongoDB集合都具有唯一的索引_id。因此,您可以删除以下行:

monitorSchema.index({_id: -1}); // Not needed
2020-07-07