在
MongoDB
中,我正在寻找一种方法来原子地更新多个文档并在一次调用中返回所有更新的文档。
我们可以在
MongoDB
中执行以下所有操作:
findAndModify
或 findOneAndUpdate
update(...{multi: true}
或 updateMany
find
我不喜欢一种方法来更新多个文档并在一次调用中将它们全部返回。有办法吗?我使用
Mongoose
作为查询包。
自动更新多个文档:
或update(...{multi: true}
updateMany
不幸的是,这是错误的:
但是,您可以通过 “使用两阶段提交方法” 来模拟事务以原子方式更新多个文档,这在此处有详细描述。
您还可以查看
$isolated
运算符,它 “防止影响多个文档的写入操作在第一个文档写入后屈服于其他读取或写入”,但它 “不提供“所有-or-nothing”写入操作的原子性”
总而言之,在 mongodb 级别(也不在驱动程序)上不可能,但您可以在应用程序级别模拟它,因此返回您需要的内容。
MongoDB v3.6 引入了会话,这使得这成为可能:https://docs.mongodb.com/manual/reference/method/Session/。我使用猫鼬,这使得使用它们非常简单。像这样的东西可能适合你的情况:
const session = await mongoose.startSession();
let result;
// NOTE `withTransaction` expects a promise so I'm using `async`
await session.withTransaction(async () => {
await SomeModel.update({
foo: "bar"
}, {
$set: {
quux:"baz"
}
}).session(session);
result = await SomeModel.find({
foo: bar
}).session(session);
});
return result;
我测试了 updateMany。
测试1:
使用updateMany更新40K文档(拉取),执行过程中,突然关闭db,然后一些节点通过(数据被拉出),一些失败(树中某些5级节点数据没有拉出),重新启动db并运行updateMany再次,所有通过并且所有数据现在都是正确的。
测试2:
在字段上创建唯一索引,插入一些数据,在 updateMany 方法中,某些文档会因为唯一键违规而失败。
我的测试2结果是:更新了零文档。
function insertData() {
const dataSource = app.models.Entity.getDataSource();
return new Promise((resolve, reject) => {
dataSource.connector.connect((err, db) => {
if (err) {
reject(new Error('.... error'));
return;
}
const entityCollection = db.collection('Entity');
// Create index
entityCollection.createIndex({ age: 1 }, { unique: true })
.then(() => {
// Insert data
const data = [
{
id: uuid.v4(),
age: 1,
type: 'test',
},
{
id: uuid.v4(),
age: 2,
type: 'test',
},
{
id: uuid.v4(),
age: 3,
type: 'test',
},
{
id: uuid.v4(),
age: 4,
type: 'test',
},
{
id: uuid.v4(),
age: 5,
type: 'test',
},
{
id: uuid.v4(),
age: 6,
type: 'test',
},
];
return insertData(data);
})
.then(() => {
resolve();
})
.catch((err2) => {
reject(err2);
});
});
});
}
function updateAge() {
const dataSource = app.models.Entity.getDataSource();
return new Promise((resolve, reject) => {
dataSource.connector.connect((err, db) => {
if (err) {
reject(new Error('...error'));
return;
}
const entityCollection = db.collection('Entity');
entityCollection.updateMany(
{ age: { $gt: 0 } },
{ $mul: { age: 2 } },
).then(() => {
resolve();
})
.catch((err2) => {
logger.error(`ERROR is ${err2}`);
reject(err2);
});
});
});
}
测试结果是:零文档更新。 “msg”:“错误是 MongoError:E11000 重复键错误集合:内容库。实体索引:age_1 dup 键:{ : 2 }”,“v”:1} 1)更新多次测试
0 通过(114ms) 1 次失败