我们目前有两个集合:Teachers
和Students
,我们限制每位教师只能拥有 30 学生,当然我已经设置了参考密钥比如每个文档上的studentsId
,teachersId
。
当我即将创建一名新学生时,如何确保我只在指定教师的学生人数少于30人时才创建?
// get students count under specify teacher
const StudentNums = await Teachers.findById(1234).studentsId.length;
if (StudentNums < 30) /* create student */
(上面的代码写的是JS)
毫无疑问,上面的代码会在并发时失败,导致mongodb共享读锁,任何想法?事先提前。
答案 0 :(得分:1)
您可以在应用程序级别应用标准Optimistic lock。
假设教师收藏中的文件有以下字段:
db.teachers.insert({
_id: 1234,
students: [12,34],
version: 8
});
其中_id
和students
是对象的有效负载,而version
是用于并发控制的内部字段。问题中的更新可能如下所示:
var teacher = db.teachers.findById(1234);
if(teacher.students.length < 30) {
var res = db.teachers.update({_id: 1234, version: teacher.version}, {$push: {students: 56}, $inc: {version: 1}});
}
if(res.nModified < 1) {
// apply retry logic
}
如果发生并发更新,则数据库中文档的版本不会与更新的查询部分匹配,这将导致更新0个文档。
答案 1 :(得分:1)
为什么不检查您的老师是否可以容纳更多学生,而不是处理与并发相关的任何事情?您可以在一次操作中检查和更新,以确保原子性。
它基本上归结为在查找查询中添加学生长度检查,如下所示:
teachers.update({id: 1, '$where': 'this.students.length < 8'}, {'$push': {students: 8}}
在下面添加了一个测试用例:
const mongodb = require('mongodb')
const mongoClient = mongodb.MongoClient
mongoClient.connect('mongodb://localhost:27017/stackoverflow', function (err, db) {
if (err) {
console.log(`DB connect error ${JSON.stringify(err)}`)
} else {
const teachers = db.collection('teachers')
let doc = {id: 1, name: 'Ms Anderson', students: [1, 2, 3, 4, 5, 6, 7], subject: 'Matrix'}
teachers.insert(doc, {w: 1}, function (err, result) {
console.log(`insert error ${JSON.stringify(err)}`)
console.log(`insert result : ${JSON.stringify(result)}`)
//Check the $where parameter, it will return only if student array has size less than 8
teachers.update({id: 1, '$where': 'this.students.length < 8'}, {'$push': {students: 8}}, {w: 1}, function (err, result) {
console.log(`update error ${JSON.stringify(err)}`)
console.log(`update result : ${JSON.stringify(result)}`)
})
})
}
})
如果文档更新,则 result
为1,否则为0。