MongoDB添加多用户触发E11000重复键错误,稀疏索引如何解决?
使用Mongoose创建用户模型时,注册第一个用户正常,但注册第二个用户时触发错误:
MongoServerError: E11000 duplicate key error collection: paytm-users.users index: friends_1 dup key: { friends: undefined }
User Schema 代码
const userSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true, trim: true, lowercase: true, minLength: 3, maxLength: 30 }, password: { type: String, required: true, minLength: 6, select: false }, firstName: { type: String, required: true, trim: true, maxLength: 50 }, lastName: { type: String, required: true, trim: true, maxLength: 50 }, friends: [{ type: mongoose.Schema.Types.ObjectId, ref: "User", sparse: true }] });
Signup 路由代码
userRouter.post('/signup', async (req, res) => { const validatedData = signupValidator.safeParse(req.body); if (validatedData.success) { const {username, firstName, lastName} = validatedData.data; const possibleUser = await UserModel.find({username: username}) if ( possibleUser.length > 0 ) { res.json({msg: "User already exists"}); console.log("user exists. signup aborted."); return } try { const hashedPassword = await bcrypt.hash(req.body.password, 10); const newUser = await UserModel.create({ username, firstName, lastName, password: hashedPassword }); await AccountModel.create({user: newUser._id, balance: 1 + Math.random()*10000}); const token = jwt.sign({ userId: newUser._id }, JWT_SECRET); res.status(200).json({ msg: "User created successfully", token: token }); } catch (err) { res.json({msg: "error occured during signup", error: err}); return } } else { res.json({ msg: "Invalid inputs "}); return } });
经排查问题出在friends字段,新用户该字段为空/undefined,但MongoDB不允许两个文档的该字段值重复。有以下疑问:
- 未在schema中给friends设置unique属性,为何会出现全局唯一限制?
- 为何两个独立文档的空字段不能重复?
- 稀疏索引(sparse index)是什么,该为哪个索引设置,如何实现以解决此问题?
1. 未设置unique却出现唯一限制的原因
大概率是你之前在MongoDB集合中手动给friends字段创建过唯一索引,或者早期Schema版本中给friends加过unique: true,后来移除但没删除集合里的旧索引。Mongoose会根据Schema自动创建索引,但如果索引已经存在,即使修改Schema也不会自动删除旧索引。
你可以通过MongoDB Shell执行命令查看集合的所有索引:
db.users.getIndexes()
如果看到friends_1索引的unique属性为true,就说明是这个旧索引导致的问题。
2. 空字段不能重复的原因
在MongoDB的唯一索引规则里,undefined(或缺失字段)会被视为相同的值。当字段被设置唯一索引后,多个文档的该字段如果都是undefined,MongoDB会把这些空值当成同一个键值,因此触发重复键错误。
3. 稀疏索引的定义及解决方法
稀疏索引的核心作用是:只包含存在指定字段的文档,不会把缺失该字段(或字段值为null/undefined)的文档纳入索引。这样多个缺失字段的文档就不会因为索引规则被判定为重复。
具体解决步骤
方法一:删除误创建的唯一索引(推荐)
你的业务场景中friends是好友列表数组,通常不需要唯一限制,直接删除friends_1索引即可解决问题:
db.users.dropIndex("friends_1")
方法二:将现有索引改为稀疏索引(仅当确实需要唯一索引时使用)
如果因为特殊需求必须保留friends的唯一索引,可以把它改成稀疏索引,执行以下命令:
db.users.createIndex( { friends: 1 }, { unique: true, sparse: true } )
修改后,稀疏唯一索引只会对存在friends字段的文档做唯一校验,缺失该字段的文档不会被索引,也就不会触发重复错误。
另外,如果你想通过Mongoose Schema确保索引配置正确,可以在Schema中明确指定(按需选择):
friends: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }] // 若需要稀疏唯一索引(不推荐你的场景): // friends: [{ // type: mongoose.Schema.Types.ObjectId, // ref: "User", // index: { unique: true, sparse: true } // }]
修改Schema后,记得删除旧集合或手动删除旧索引,让Mongoose重新创建正确的索引。
内容的提问来源于stack exchange,提问作者user24956669

