我的Mongoose模式使用custom_id值,我继承的代码执行类似于以下操作
const sampleSchema = new mongoose.Schema({
_id: String,
key: String,
});
sampleSchema.statics.generateId = async function() {
let id;
do {
id = randomStringGenerator.generate({length: 8, charset: 'hex', capitalization: 'uppercase'});
} while (await this.exists({_id: id}));
return id;
};
let SampleModel = mongoose.model('Sample', sampleSchema);
一个简单的用法如下:
let mySample = new SampleModel({_id: await SampleModel.generateId(), key: 'a' });
await mySample.save();
这至少有三个问题:
- 每次保存都需要至少两次访问数据库,一次用于测试唯一ID,另一次用于保存文档。
- 要使其工作,必须在每次保存之前手动调用
generateId()
,一个理想的解决方案可以为我处理这个问题,就像Mongoose处理ObjectId类型的id一样。 - 最重要的是,存在一个可能导致
duplicate key error
的争用条件。假设有两个客户端运行此代码。两个客户端都碰巧同时生成相同的ID,两个客户端都在数据库中查找并发现ID不存在,两个客户端都尝试将记录写入数据库。第二个客户端将失败。
一个理想的解决方案是,在保存时,生成一个id,将其保存到数据库中,并在重复键错误时,生成一个新的id并重试。循环执行此操作,直到文档成功存储。问题是,我不知道如何让Mongoose让我这样做。
以下是我的尝试:基于这个SO Question,我发现了一个相当老的覆盖save函数以完成类似操作的示例(使用非常老的mongoose版本),并基于它进行了此尝试。
// First, change generateId() to force a collision
let ids = ['a', 'a', 'a', 'b'];
let index = 0;
let generateId = function() {
return ids[index++];
};
// Configure middleware to generate the id before a save
sampleSchema.pre('validate', function(next) {
if (this.isNew)
this._id = generateId();
next();
});
// Now override the save function
SampleModel.prototype.save_original = SampleModel.prototype.save;
SampleModel.prototype.save = function(options, callback) {
let self = this;
let retryOnDuplicate = function(err, savedDoc) {
if (err) {
if (err.code === 11000 && err.name === 'MongoError') {
self.save(options, retryOnDuplicate);
return;
}
}
if (callback) {
callback(err, savedDoc);
}
};
return self.save_original(options, retryOnDuplicate);
}
这让我接近但我泄漏了一个承诺,我不知道在哪里。
let sampleA = new SampleModel({key: 'a'});
let sampleADoc = await sampleA.save();
console.log('sampleADoc', sampleADoc); // prints undefined, but should print the document
let sampleB = new SampleModel({key: 'b'});
let sampleBDoc = await sampleB.save();
console.log('sampleBDoc', sampleBDoc); // prints undefined, but should print the document
let all = await SampleModel.find();
console.log('all', all); // prints `[]`, but should be an array of two documents
产出
sampleADoc undefined
sampleBDoc undefined
all []
文档最终会写入数据库,但在调用console.log之前不会写入。
我在哪里泄露了一个承诺?有没有更简单的方法来解决我概述的三个问题?
编辑1: Mongoose 版本:5.11.15
1条答案
按热度按时间izj3ouym1#
我通过修改保存覆盖来修复这个问题。完整的解决方案如下所示:
这将重复生成
_id
,直到调用成功的保存并解决原始问题中列出的三个问题:为了改善这一点:
_id
,或者您可能有多个字段需要唯一的生成值。可以更新嵌入的帮助函数isDupKeyError()
以查找多个键。然后,在出错时,您可以添加逻辑以仅重新生成失败的键。