在MongoDB Mongoose中,E11000重复键错误索引

361

以下是我的user.js模型中的user架构 -

var userSchema = new mongoose.Schema({
    local: {
        name: { type: String },
        email : { type: String, require: true, unique: true },
        password: { type: String, require:true },
    },
    facebook: {
        id           : { type: String },
        token        : { type: String },
        email        : { type: String },
        name         : { type: String }
    }
});

var User = mongoose.model('User',userSchema);

module.exports = User;

这是我在控制器中使用它的方式 -

var user = require('./../models/user.js');

这是我将其保存在数据库中的方式 -

user({'local.email' : req.body.email, 'local.password' : req.body.password}).save(function(err, result){
    if(err)
        res.send(err);
    else {
        console.log(result);
        req.session.user = result;
        res.send({"code":200,"message":"Record inserted successfully"});
    }
});

错误 -

{"name":"MongoError","code":11000,"err":"insertDocument :: caused by :: 11000 E11000 duplicate key error index: mydb.users.$email_1  dup key: { : null }"} 

我检查了数据库集合,没有这样的重复条目,请告诉我我做错了什么?

FYI - req.body.emailreq.body.password 正在获取值。

我还查看了这篇帖子,但没有帮助:STACK LINK

如果我完全删除它,那么它会插入文档,否则它会抛出“重复”错误,即使我在本地邮件中已经有一个条目。


4
遇到了同样的错误!在开发过程中,我们禁用了自动索引并使用了小写的模式名称/键。后来我们决定改用首字母大写的方式,但没有更新我们的索引名称(即:titleCase而不是TitleCase)。因此,当我们启用索引时,就会出现这个错误。花了一点时间才弄清楚问题所在。您需要确保所有的名称/键在各处都完全相同。 - Jeach
36
我遇到了同样的错误,将mongoose模型设置为unique: false没有任何影响。我意识到我首先需要删除表格,然后它才能正常工作。你可以执行类似于db.whateverthecollection.drop({})的操作。请注意,这会删除整个集合。 - Pere
2
_id: mongoose.Types.ObjectId 是必须添加的,因为需要一个唯一的标识符。 - Mayank Pandav
1
@Pere 我不相信这个起作用了,谢谢。 - Vinita
24个回答

340
错误信息指出已经存在一条记录的电子邮件为空。换句话说,您已经有一个没有电子邮件地址的用户。
相关文档如下:
如果一个文档在唯一索引中没有为索引字段设置值,则该索引将为此文档存储 null 值。由于唯一约束性,MongoDB 仅允许缺少索引字段的一个文档。如果有超过一个文档没有为索引字段设置值或者缺少索引字段,则索引构建将因重复键错误而失败。您可以将唯一约束和稀疏索引组合使用,从唯一索引中过滤这些空值并避免出现错误。unique indexes 稀疏索引仅包含具有索引字段的文档条目,即使索引字段包含 null 值。换句话说,具有多个 null 值的文档对于稀疏索引也可以接受。sparse indexes

来自评论:

你的错误显示键名为mydb.users.$email_1,这让我怀疑你在users.emailusers.local.email上都有索引(前者目前已经过时且未使用)。从Mongoose模型中删除字段不会影响数据库。如果是这种情况,请检查mydb.users.getIndexes()并手动使用mydb.users.dropIndex(<name>)删除不需要的索引。


2
我刚刚删除了那个空文档,现在它可以工作了 :) +1 ..谢谢你的帮助 - Trialcoder
80
您的错误提示说明键名为 mydb.users.$email_1,这让我怀疑您在users.emailusers.local.email上都建立了索引(前者目前已经老旧且未使用)。从Mongoose模型中删除一个字段不会影响数据库。如果是这种情况,请使用 mydb.users.getIndexes() 进行检查并手动使用 mydb.users.dropIndex(<name>) 删除不需要的索引。 - RickN
23
如果你正在进行多个索引更改,可以使用db.users.dropIndexes()命令。请注意,该命令将删除集合中的所有索引。 - cs_stackX
2
当我在使用mongoose插件passport-local-mongoose时,为新用户没有设置用户名是一个问题。 - sshow
1
这是一个关于观点或者甚至是意见的问题,@titoih - 是 null 还是缺少一个值 就像任何其他值一样 或者它是一个特殊情况?"a@b.c" 和 "a@b.c" 是相同的,那么 "nothing" 和 "nothing" 也是相同的吗?在 MongoDB 中,对于非稀疏索引,答案是 "是"。而其他数据库(如 MySQL)则会说 "否"。 - RickN
显示剩余5条评论

97

如果您仍在开发环境中,我建议将整个数据库删除,并使用新的模式重新开始。

从命令行执行

➜ mongo
use dbName;
db.dropDatabase();
exit

77
我认为这个解决方案非常激进,我认为按照cs_stackX所说的db.collection.dropIndexes()已经足够了。 - JorgeGarza
6
我有相同的问题。我只是删除了集合,之后一切都正常工作了。 - Sandip Subedi
1
我意外地为我的两个字段使用了唯一Id来初始化集合。后来,当我想要添加具有相同Id的多个文档时,它不允许我这样做,直到我删除了集合并重新访问它。 - Meir Snyder
1
如果你是开发的新手,这是一个很好的机会去尝试和找出如何解决这个问题...而不是在你已经存储了千兆字节的用户数据后再遇到这个问题。 - Janac Meena
1
@JorgeGarza 的评论很有道理。当服务器重新启动时,模式文件会再次重建已删除的索引。此外,它起作用了。 - retr0
谢谢,@JorgeGarza,你的评论点亮了我的脑海,我刚刚删除了模型并重试,现在它可以工作了。 - Md. Jamal Uddin

55
我想用小学五年级的语言解释答案/解决方案,让每个人都能理解。
我有一个应用程序。我希望人们使用他们的电子邮件、密码和电话号码进行注册。在我的MongoDB数据库中,我想根据他们的电话号码和电子邮件来唯一地识别每个人,这意味着每个人的电话号码和电子邮件都必须是唯一的。
然而,有一个问题:我意识到每个人都有电话号码,但并不是每个人都有电子邮件地址。
那些没有电子邮件地址的人向我保证,他们下周会有一个电子邮件地址。但无论如何,我都希望他们进行注册,所以我告诉他们继续注册他们的电话号码,同时将电子邮件输入框留空。
他们这样做了。
我的数据库需要一个唯一的电子邮件地址字段,但有很多人的电子邮件地址为“null”。因此,我去我的代码中告诉数据库模式允许空/ null电子邮件地址字段,然后等那些承诺在下周将其电子邮件添加到其个人资料中的人填写电子邮件地址后,我再将其替换为唯一值。
所以现在对于每个人都是双赢(除了你):人们可以注册,我很高兴拥有他们的数据...我的数据库也很高兴因为它被很好地使用了...但是你呢?我还没有给你制作模式的代码。
以下是代码: 注意:电子邮件中的稀疏属性告诉我的数据库允许空值,这些空值在后来将被填充为唯一值。
var userSchema = new mongoose.Schema({
  local: {
    name: { type: String },
    email : { type: String, require: true, index:true, unique:true,sparse:true},
    password: { type: String, require:true },
  },
  facebook: {
    id           : { type: String },
    token        : { type: String },
    email        : { type: String },
    name         : { type: String }
  }
});

var User = mongoose.model('User',userSchema);

module.exports = User;
我希望我已经解释得很清楚。 祝你在NodeJS编码/黑客方面愉快!


如果您想要一个“稀疏”的索引和“唯一”的验证,并且有其他是必需的字段,您需要使用partialFilterExpression选项。请参见此答案https://dev59.com/-YTba4cB1Zd3GeqP_tRx#34600171 - Gianfranco P.

47

在这种情况下,登录Mongo并查找您不再使用的索引(在 OP 的情况下是“email”)。然后选择删除索引 输入图像描述


4
感谢这个图形化的解决方案!完美地解决了问题。 - xv8
谢谢,它完美地运作了。这是我找到的唯一解决方案。 - Roshan Wickramaarachchi
谢谢,这对我有用。在我的情况下,我只是删除了电子邮件和电话的索引。它对我起作用了。 - Dulal Sandip
对我来说完美地解决了问题。谢谢你的解决方案。 - Adeena Lathiya

33

检查集合索引。

由于字段应该存储在不同的新路径中,我的集合中的索引已过时,导致出现了这个问题。

Mongoose在将字段指定为唯一时添加索引。


25

基本上,这个错误是说,你在一个特定字段上有一个唯一索引,比如:"email_address",所以mongodb期望每个文档在集合中具有唯一的电子邮件地址值。

假设,在你的模式中未定义唯一索引之前,你使用相同的电子邮件地址或没有电子邮件地址(null值)注册了2个用户。

随后,你发现出现了错误。因此,你尝试通过将唯一索引添加到模式中来进行更正。但是你的集合已经有重复项,因此错误消息指出你不能再次插入重复的值。

你实际上有三个选项:

  1. 删除集合

    db.users.drop();

  2. 查找具有该值的文档并将其删除。假设该值为null,则可以使用以下命令将其删除:

    db.users.remove({ email_address: null });

  3. 删除唯一索引:

    db.users.dropIndex(indexName)

希望这有所帮助 :)


24

编辑:这个解决方案在2023年仍然有效,您不需要删除集合或丢失任何数据。

以下是我在2020年9月解决同样问题的超快速且易于使用的方法。从mongodb atlas(云和桌面)中可能很容易?这就是为什么我觉得我应该在2020年写这篇答案的原因。

首先,我读到了上面一些关于更改mongoose模式中“unique”字段的建议。如果您遇到此错误,我假设您已经更改了模式,但尽管如此,您的响应仍为500,并注意到这一点:指定重复的KEY!如果问题是由模式配置引起的,并且假设您已经配置了一个体面的中间件来记录mongo错误,则响应将为400。

为什么会发生这种情况(至少是主要原因)

为什么会这样?在我的情况下很简单,模式上的那个字段过去只接受唯一值,但我刚刚将其更改为接受重复值。Mongodb 为具有唯一值的字段创建索引以便更快地检索数据,因此在过去,mongo为该字段创建了该索引,因此即使在模式上将“unique”属性设置为“false”,mongodb仍在使用该索引,并将其视为必须唯一。

如何解决它

删除该索引。您可以从Mongo Atlas或在mongo shell上执行命令中的2秒钟内完成。为简单起见,我将向不使用mongo shell的用户展示第一个。

转到您的集合。默认情况下,您在“查找”选项卡上。只需选择右侧的下一个选项卡:“索引”。您将看到仍然有一个索引赋给相同的字段,这会给您带来麻烦。只需单击“删除索引”按钮即可。完成。

因此,每次发生这种情况时都不要删除数据库

我认为这比仅删除整个数据库甚至集合更好。基本上,这就是为什么在删除整个集合后它能够工作的原因。因为如果您的第一个条目使用了具有“unique:false”的新模式,则mongo不会为该字段设置索引。

Server response

Cloud Atlas


7

4
这是我的相关工作经验:
在“用户”架构中,我将“姓名”设为唯一键,然后运行了一些操作,我认为这样设置了数据库结构。
然后我将唯一键更改为“用户名”,并且在保存数据到数据库时不再传递“姓名”值。所以MongoDB可能会自动将新记录的“姓名”值设置为重复键的null。我尝试在“用户”架构中将“姓名”键设置为不是唯一键{name: {unique: false, type: String}}以覆盖原始设置。但是,它没有起作用。
最后,我想出了自己的解决方案:
当您保存数据记录时,只需将一个不太可能重复的随机键值设置为“姓名”键即可。简单的数学方法'' + Math.random() + Math.random()生成随机字符串。

14
我认为这不是一个有效的解决方案,你只是掩盖了问题。 - Rick
1
丑陋但实用 - Anthony

3

我已经通过这种方式解决了我的问题。

只需进入您的 mongoDB 账户 -> Atlast 集合,然后删除您的数据库列。或者进入 mongoDB compass 然后删除您的数据库,

有时当您在数据库中保存了一些空值时,会发生这种情况。


FYI - 你不需要删除数据库。你可以进入Compass,然后进入集合,再进入索引选项卡,就可以直接删除那个索引。 - Zohar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接