Lưu ý quan trọng: MongoDB **hỗ trợ Multi-document ACID Transactions từ phiên bản 4.0** (Replica Set) và 4.2 (Sharded Cluster). Bài viết gốc nói "không hỗ trợ" là đã cũ. Tuy nhiên, mẫu thiết kế findOneAndUpdate để mô phỏng khóa (lock) ứng dụng (application-level lock) vẫn hữu ích cho các phiên bản cũ hoặc trường hợp không muốn dùng transaction đầy đủ.
1. Tạo bộ sưu tập và chèn dữ liệu
> db.demo404.insertOne({ "FirstName": "John" });
> db.demo404.insertOne({ "FirstName": "Robert" });
> db.demo404.insertOne({ "FirstName": "Mike" });
2. Xem dữ liệu
> db.demo404.find();
Kết quả: 3 tài liệu John, Robert, Mike.
3. Mô phỏng Lock bằng findOneAndUpdate (Atomic)
Ý tưởng: Thêm trường in_transaction: true vào tài liệu đầu tiên **chưa bị khóa** ($exists: false). Thao tác này **atomic** - chỉ 1 client thành công.
> var result = db.demo404.findOneAndUpdate(
{ "in_transaction": { $exists: false } },
{ $set: { "in_transaction": true } }
);
4. Kết quả: Trả về tài liệu ĐÃ KHÓA (trước khi update)
{ "_id": ObjectId("5e6f8c38fac4d418a0178592"), "FirstName": "John" }
Tài liệu này giờ có in_transaction: true. Client khác chạy lại sẽ nhận tài liệu tiếp theo (Robert).
5. Giải phóng Lock
> db.demo404.updateOne(
{ _id: result._id },
{ $unset: { "in_transaction": "" } }
);
So sánh với Transaction thật (MongoDB 4.0+):
const session = db.getMongo().startSession();
session.startTransaction();
try {
db.demo404.updateOne({ _id: id1 }, { $set: { ... } }, { session });
db.demo404.updateOne({ _id: id2 }, { $set: { ... } }, { session });
session.commitTransaction();
} catch (e) {
session.abortTransaction();
} finally {
session.endSession();
}
Khi nào dùng pattern findOneAndUpdate lock? Hệ thống phân tán đơn giản, rate limiting, leader election, hoặc MongoDB < 4.0.