写在最前,我使用的 mongoose版本:v7.5.1 ,MongoDB版本: v7.0.0
问题描述
最近使用express + mongoose + MongoDB 做自己的博客后端时,遇到了这样一个问题 新增文档时,集合中插入第一条文档正常,插入第二条时候报错key重复:
Error: 11000 duplicate key error collection: xxx index: keyName dup key: { yyy: "zzz" }
经过对数据库和mongoose模型、插入过程的排查,终于解决了问题。经过一系列测试,crud均正常。
修完bug后我又尝试了一番,大体上把我发现的导致问题的因素分为以下两大类:
为方便理解,本文中我们将
Schema
中定义的mongoose.Types.ObjectId
类型称为主键,或者key、id ,在MongoDB中,默认自动生成的是_id
第一类 - 自己指定了id
- 情况一,每次添加手动设置了id的值:
由于自己在调试过程中创建了多个名字的主键,但自己因为某种原因遗忘了(比如命名调整,比如忘了删除index),后面又重新创建了与之前重名的主键,而MongoDB中存在同名未删除的主键,导致冲突。这种情况通过重置数据库对应集合中Index
消除,代价是要删除本集合中之前全部数据
解决办法:进入数据库
js
db.collectionName.getIndexes() // 查看存在的索引名, 多半有之前重名的
db.collectionName.dropIndexes() // 删除全部索引, 删除之前的文档,重新创建
- 情况二,插入或许正常,但是使用自己id时候发现处理到了别的文档:
自己指定了id,由于在new Schema()
中,给指定的id设置了默认值,比如:
js
const mongoose = require("../DB")
var Schema = mongoose.Schema
var tagSchema = new Schema({
tagId: {
type: mongoose.ObjectId,
default: mongoose.Types.ObjectId(),
unique: true // 这个要有
},
publishTime: Date,
updateTime: Date,
userId: String,
tagname: String,
tagIcon: String,
hasNums: Number
})
const tagModel = mongoose.model('tags', tagSchema)
module.exports = { tagModel }
但是,重点:插入数据时候没有手动给你设置的id赋值
,导致了,Model使用的是设置Schema
时候初始化的默认值,这就导致了写入数据库时候的值是初始化时候的值,就和之前的一样了,这时如果你的命名不是_id, 那么数据库依然会生成_id
,新增或许可以成功,但是一旦编辑数据,就可能改掉了别的文档的数据,因为根据你设置的id,其他文档也有相同值的id,如果只使用id约束,那么改到别的文档在所难免。
解决方法是:新增数据时候手动赋值
js
const { tagModel } = require('../model/tag')
const mongoose = require('mongoose')
const { ObjectId } = mongoose.Types
let flag = false
// 操作都是是异步的,要await
await tagModel.create({
tagId: new ObjectId(), // 手动赋值,生成新的key
userId, userId,
tagname: name,
tagIcon: icon,
hasNums: 0,
publishTime:birth,
updateTime: 0
}).then((data) => {
console.log('insert success')
flag = true
}).catch((err) => {
if(err.message.indexOf('duplicate key error')!==-1){
console.log('存在重复key', err.keyPattern)
}else {
Object.entries(err).map(([key, value]) => {
console.log(`error: ${key}, ${value.message}`)
})
}
flag = false
})
return false
- 情况三:
自己设置了id,并且报错信息显示的是自己设置过的id,其值在数据库中也存在,那就是生成了和之前id同值的新id了,需要自己去处理,数据库删除废id或者修改,或者改进生成id的算法避免出现重复值
第二类 - 自己没有指定id
比如这样:
js
var tagSchema = new Schema({
/* 没有指定_id, 那么MongoDB会自动生成_id */
publishTime: Date,
updateTime: Date,
userId: String,
tagname: String,
tagIcon: String,
hasNums: Number
})
如果没有手动设置类型为mongoose.Schema.Types.ObjectId
或者mongoose.ObjectId(注意版本)
类型的key , MongoDB会默认自己生成一个_id
作为key,这个key就像很多文章里说的,他的生成受到 时间戳+主机+进程号+序列 的影响,影响他生成的内外原因如下:
- 内部原因:同时插入两条数据,导致数据库生成了同一个id值。
- 外部原因:每次使用同一个变量存储不同的数据,导致数据库认为每次存储的是同一条数据,最终生成同一个id值。
所以需要自己指定id,请注意,指定之前还有个步骤是清除数据库中的索引记录
js
db.yourCollectionName.dropIndexes()
然后再手动设置id
js
var tagSchema = new Schema({
tagId: {
type: mongoose.ObjectId,
unique: true // 这个要有
},
publishTime: Date,
updateTime: Date,
userId: String,
tagname: String,
tagIcon: String,
hasNums: Number
})
最后记得插入时手动
js
tagId: new mongoose.Types.ObjectId()