In MongoDB
, I'm looking for a way to ATOMICally update multiple documents and return all of the updated documents in a single call.
We can do all of the following in MongoDB
:
- Atomically update one document and return the updated document:
findAndModify
or findOneAndUpdate
- Atomically update multiple documents:
update(...{multi: true}
or updateMany
- Query and return multiple documents:
find
I haven't fond a way to update multiple documents and return them all in one call. Is there a way? I'm using Mongoose
as the querying package.
Atomically update multiple documents: update(...{multi: true}
or
updateMany
Unfourtantely that is false:
In MongoDB, write operations, e.g. db.collection.update(),
db.collection.findAndModify(), db.collection.remove(), are atomic on
the level of a single document.
In MongoDB, a write operation is atomic on the level of a single
document, even if the operation modifies multiple embedded documents
within a single document.
However, you can simulate a transaction to atomically update multiple documents by "using a two-phase commit approach" which is described in detail there.
You may also look at the $isolated
operator, which "prevents a write operation that affects multiple documents from yielding to other reads or writes once the first document is written" but it "does not provide “all-or-nothing” atomicity for write operations"
As a summary, it is not possible at mongodb level (nor the driver), but you can simulate it at your application level therefore return what you need.
I tested updateMany.
Testing1 :
Update 40K documents with updateMany (pull) , during execution , shutdown db suddenly , then some nodes passed(data was pulled out) , some failed (data was not pulled out in some level 5 nodes in tree) , restart db and run updateMany again , all passed and all data are correct now.
Testing 2:
Create an unique index on a field, insert some data, in updateMany method, some document will failed because of unique key violation.
My test2 result is : zero document was updated.
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);
});
});
});
}
The test result is : Zero documents are updated.
"msg":"ERROR is MongoError: E11000 duplicate key error collection: content-base.Entity index: age_1 dup key: { : 2 }","v":1}
1) updateMany test
0 passing (114ms)
1 failing