使用upsert

时间:2016-05-18 09:34:58

标签: node.js mongodb mongoose duplicates

我遇到重复密钥的问题。 好久不能找到答案。请帮我解决这个问题或解释为什么我会出现重复的密钥错误。

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));
};

1 个答案:

答案 0 :(得分:19)

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

  1. 查询要识别的已识别文档。
  2. 如果文档存在,则自动更新现有文档。
  3. 否则(文档不存在),自动插入包含查询字段和更新的新文档。
  4. 因此,步骤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);
            }
        }
    });
    

    有关相关文档,请参阅here

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

    此外,您无需在_id上手动创建索引,因为所有MongoDB集合都在_id上具有唯一索引,无论如何。所以你可以删除这一行:

    _id