ใช้ findOneAndUpdate() แทน Transaction ใน MongoDB 4.0
MongoDB 4.0 ยังไม่รองรับ Multi-document Transaction (รองรับตั้งแต่ 4.0 สำหรับ Replica Set และ 4.2 สำหรับ Sharded Cluster) สำหรับกรณีที่ต้องการ Lock เอกสารเพื่อป้องกัน Race Condition สามารถใช้ findOneAndUpdate() ซึ่งทำงานแบบ Atomic
ตัวอย่างข้อมูลทดสอบ
> db.demo404.insertOne({ "FirstName": "John" });
> db.demo404.insertOne({ "FirstName": "Robert" });
> db.demo404.insertOne({ "FirstName": "Mike" });
แสดงข้อมูลทั้งหมด
> db.demo404.find();
ผลลัพธ์:
{ "_id": ObjectId("..."), "FirstName": "John" }
{ "_id": ObjectId("..."), "FirstName": "Robert" }
{ "_id": ObjectId("..."), "FirstName": "Mike" }
คำสั่ง Lock เอกสารแรกที่ยังไม่มี in_transaction
> result = db.demo404.findOneAndUpdate(
{ "in_transaction": { $exists: false } },
{ $set: { "in_transaction": true } }
);
ผลลัพธ์ที่ได้
{ "_id": ObjectId("5e6f8c38fac4d418a0178592"), "FirstName": "John" }
คำอธิบาย
- findOneAndUpdate(): ค้นหาเอกสาร 1 รายการ อัปเดต และคืนค่าเอกสารเดิม (ก่อนอัปเดต) พร้อมกันแบบ Atomic
- { "in_transaction": { $exists: false } }: หาเอกสารที่ยังไม่มีฟิลด์ in_transaction (ยังไม่ถูก lock)
- { $set: { "in_transaction": true } }: ตั้งค่า in_transaction = true เพื่อ lock
รูปแบบการใช้งาน Lock Pattern
- พยายาม Lock:
findOneAndUpdate({ in_transaction: { $exists: false } }, { $set: { in_transaction: true, lockedBy: "processId", lockedAt: new Date() } }) - ถ้าสำเร็จ (คืนเอกสารไม่ใช่ null) -> ทำงานต่อ
- เสร็จสิ้น ->
updateOne({ _id: doc._id }, { $unset: { in_transaction: "", lockedBy: "", lockedAt: "" } })เพื่อปลด Lock - ถ้าล้มเหลว (คืน null) -> รอหรือลองใหม่
ข้อจำกัด
- ทำงานได้เฉพาะ Single Document เท่านั้น
- ต้องจัดการ Timeout/Deadlock เอง (เช่น TTL Index บน lockedAt)
- สำหรับ Multi-document Transaction จริง ให้อัปเกรด MongoDB 4.0+ (Replica Set) หรือ 4.2+ (Sharded)