【问题标题】:Trying to update and ignore duplicate key, by db.XYZ.update fails尝试通过 db.XYZ.update 更新和忽略重复键失败
【发布时间】:2012-04-12 03:42:12
【问题描述】:

我在尝试修复 MongoDB 中的某些对象时遇到了一些奇怪的行为。我正在尝试将语言代码 (lc) 从 may 更改为 msa,并且我有一个关于文本和语言代码的唯一索引,例如{t:1, lc:1}

首先我得到计数​​:

db.Unit.count({lc: "may"});

我试试:

db.Unit.find({lc: "may"}, {"t":1}).limit(1000).forEach(function(obj) {
    try {
         db.Unit.update({ _id: obj._id }, {$set : { "lc": "msa"}} );
         print('Changed :' + obj.t + '#' + obj._id);
    } catch (err) {
        print(err);
    }
});

这似乎可以打印出很多对象,然后失败:

E11000 duplicate key error index: jerome5.Unit.$t_1_lc_1  dup key: { : "laluan", : "msa" }

现在我预计失败前的匹配项会正确更新,但计数返回的数字完全相同。

我的 Javascript 是否遗漏了一些明显的东西?

更新:看起来有些对象打印出来没有抛出异常也是重复的。所以看起来在引发错误之前有一些延迟(我启用了日记功能)。这是正常行为吗?

【问题讨论】:

    标签: mongodb


    【解决方案1】:

    简短的回答是问题出在 JS 代码上。

    默认情况下,Mongo 中的更新是触发后忘记的,因此即使单个更新由于重复键而失败,“try”语句仍然会成功完成,并且“catch”部分中的代码将永远不会执行.可能看起来“catch”代码正在执行,因为当 forEach 循环结束时,JS shell 返回 db.getLastError(),如果操作成功,它将返回 null。 GetLastError 在此处的文档中进行了说明: http://www.mongodb.org/display/DOCS/getLastError+Command

    这也许最好通过示例来解释:

    让我们创建一个简单的集合和一个唯一索引:

    > db.unit.save({_id:0, lc: "may", t:0})
    > db.unit.ensureIndex({t:1, lc:1}, {unique:true})
    > for(var i=1; i<10; i++){db.unit.save({_id:i, lc: "may", t:i})}
    > db.unit.find()
    { "_id" : 0, "lc" : "may", "t" : 0 }
    { "_id" : 1, "lc" : "may", "t" : 1 }
    { "_id" : 2, "lc" : "may", "t" : 2 }
    { "_id" : 3, "lc" : "may", "t" : 3 }
    { "_id" : 4, "lc" : "may", "t" : 4 }
    { "_id" : 5, "lc" : "may", "t" : 5 }
    { "_id" : 6, "lc" : "may", "t" : 6 }
    { "_id" : 7, "lc" : "may", "t" : 7 }
    { "_id" : 8, "lc" : "may", "t" : 8 }
    { "_id" : 9, "lc" : "may", "t" : 9 }
    >
    

    我们将运行一个脚本来将所有“may”值更改为“msa”。在我们做之前,让我们做一些更改,因此将“may”的某些值更改为“msa”会在索引中创建重复值:

    > db.unit.update({_id: 3}, {"lc" : "msa", "t" : 4 })
    > db.unit.update({_id: 6}, {"lc" : "msa", "t" : 5 })
    > db.unit.find()
    { "_id" : 0, "lc" : "may", "t" : 0 }
    { "_id" : 1, "lc" : "may", "t" : 1 }
    { "_id" : 2, "lc" : "may", "t" : 2 }
    { "_id" : 3, "lc" : "msa", "t" : 4 }
    { "_id" : 4, "lc" : "may", "t" : 4 }
    { "_id" : 5, "lc" : "may", "t" : 5 }
    { "_id" : 6, "lc" : "msa", "t" : 5 }
    { "_id" : 7, "lc" : "may", "t" : 7 }
    { "_id" : 8, "lc" : "may", "t" : 8 }
    { "_id" : 9, "lc" : "may", "t" : 9 }
    > 
    

    现在,当我们的脚本命中文档 _id:4 和 _id:5 时,它将无法将“lc”的值更改为“may”,因为这样做会在索引中创建重复条目。

    让我们运行脚本的一个版本。我添加了一些额外的行以使其更详细:

    db.unit.find({lc: "may"}, {"t":1}).limit(1000).forEach(function(obj) {
        try {
            print("Found _id: " + obj._id );
            db.unit.update({ _id: obj._id }, {$set : { "lc": "msa"}} );
            if(db.getLastError() == null){
                print('Changed t :' + obj.t + ' _id : ' + obj._id);
            }
            else{
                print("Unable to change _id : " + obj.id + " because: " + db.getLastError())
            }
        } catch (err) {
            print("boo");
            print(err);
        }
    });
    
    Found _id: 0
    Changed t :0 _id : 0
    Found _id: 1
    Changed t :1 _id : 1
    Found _id: 2
    Changed t :2 _id : 2
    Found _id: 4
    Unable to change _id : undefined because: E11000 duplicate key error index: test.unit.$t_1_lc_1  dup key: { : 4.0, : "msa" }
    Found _id: 5
    Unable to change _id : undefined because: E11000 duplicate key error index: test.unit.$t_1_lc_1  dup key: { : 5.0, : "msa" }
    Found _id: 7
    Changed t :7 _id : 7
    Found _id: 8
    Changed t :8 _id : 8
    Found _id: 9
    Changed t :9 _id : 9
    > 
    

    如您所见,“boo”从未打印,因为“catch”代码从未执行,即使两条记录无法更新。从技术上讲,update() 没有失败,它只是因为重复的索引条目而无法更改文档,并生成了一条消息。

    所有可更改的记录均已成功更改。

    > db.unit.find()
    { "_id" : 0, "lc" : "msa", "t" : 0 }
    { "_id" : 1, "lc" : "msa", "t" : 1 }
    { "_id" : 2, "lc" : "msa", "t" : 2 }
    { "_id" : 3, "lc" : "msa", "t" : 4 }
    { "_id" : 4, "lc" : "may", "t" : 4 }
    { "_id" : 5, "lc" : "may", "t" : 5 }
    { "_id" : 6, "lc" : "msa", "t" : 5 }
    { "_id" : 7, "lc" : "msa", "t" : 7 }
    { "_id" : 8, "lc" : "msa", "t" : 8 }
    { "_id" : 9, "lc" : "msa", "t" : 9 }
    

    如果再次运行脚本,将生成以下输出:

    Found _id: 4
    Unable to change _id : undefined because: E11000 duplicate key error index: test.unit.$t_1_lc_1  dup key: { : 4.0, : "msa" }
    Found _id: 5
    Unable to change _id : undefined because: E11000 duplicate key error index: test.unit.$t_1_lc_1  dup key: { : 5.0, : "msa" }
    E11000 duplicate key error index: test.unit.$t_1_lc_1  dup key: { : 5.0, : "msa" }
    >
    

    如您所见,最后一条错误消息被打印了两次:一次是在我们的脚本中打印,另一次是在脚本完成时。

    请原谅此回复的冗长性质。我希望这能提高您对 getLastError 以及如何在 JS shell 中执行操作的理解。

    脚本可以在没有try/catch语句的情况下重写,简单的打印出任何无法更新的文档的_ids:

    db.unit.find({lc: "may"}, {"t":1}).limit(1000).forEach(function(obj) {
        print("Found _id: " + obj._id );
        db.unit.update({ _id: obj._id }, {$set : { "lc": "msa"}} );
        if(db.getLastError() == null){
            print('Changed t :' + obj.t + ' _id : ' + obj._id);
        }
        else{
            print("Unable to change _id : " + obj.id + " because: " + db.getLastError())
        }
    });
    

    【讨论】:

    • 感谢 Marc 抽出时间给出如此明确的答复。 db.getLastError() 的例子正是我所需要的!
    猜你喜欢
    • 1970-01-01
    • 2021-10-28
    • 2016-01-19
    • 1970-01-01
    • 2015-04-27
    • 2021-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多