ข้ามไปยังเนื้อหา

การลบ document

การลบคือ operation ที่มี syntax น้อยที่สุดแต่เดิมพันสูงที่สุด ไม่มี operator ให้เรียนรู้ — คุณยื่น filter ให้ MongoDB แล้ว document ที่ match ก็หายไป ทักษะทั้งหมดจึงอยู่ที่ filter เพราะการลบจะแม่นยำได้เท่าที่เงื่อนไขคุณแม่นเท่านั้น และ filter ที่กว้างเกินไปจะลบข้อมูลที่คุณตั้งใจจะเก็บไว้ ข่าวดีคือ method เหล่านี้สะท้อนทุกสิ่งที่คุณรู้อยู่แล้ว นั่นคือ deleteOne ลบรายการแรกที่ตรงกัน ส่วน deleteMany ลบทุกรายการที่ตรงกัน

เราดำเนินต่อด้วย collection members ทุกตัวอย่างแสดงว่า filter เลือกอะไรและ acknowledgement รายงานอะไร

deleteOne ลบ document แรก ที่ตรงกับ filter แล้วหยุด แม้ว่าจะมีรายการอื่นตรงกันด้วยก็ตาม ราวกันตกของ method แบบลบทีละอันนี่แหละคือเหตุผลที่ควรหยิบมาใช้เวลาตั้งใจลบระเบียนเดียวแบบเจาะจง ในที่นี้เราลบสมาชิกชื่อ Edsger:

db.members.deleteOne({ name: "Edsger" })

acknowledgement รายงานว่า document ถูกลบไปกี่รายการ:

{ "acknowledged": true, "deletedCount": 1 }

ถ้า filter ไม่ตรงกับอะไรเลย นั่นไม่ใช่ error — deletedCount ก็เพียงแค่เป็น 0:

{ "acknowledged": true, "deletedCount": 0 }

ใน Compass: เอาเมาส์ไปวางบน document ในแท็บ Documents แล้วคลิกไอคอนถังขยะ จากนั้นยืนยัน Compass จะลบ document รายการเดียวนั้น

deleteMany ลบ document ทุก รายการที่ตรงกับ filter ใน operation เดียว ในที่นี้เรากวาดล้างทุกคนที่เข้าร่วมก่อนปี 2024 — สังเกตว่า filter เป็นตัวทำงานเรื่องความแม่นยำ:

db.members.deleteMany({ joined: { $lt: 2024 } })

จำนวนนับสะท้อนทุก document ที่ filter กวาดเข้ามา:

{ "acknowledged": true, "deletedCount": 2 }

บางครั้งการลบแบบถาวร (hard delete) เป็นตัวเลือกที่ผิด คุณอาจต้องการเส้นทางการตรวจสอบ (audit trail) ความสามารถในการ undo หรือต้องการให้รายงานเชิงประวัติแม่นยำ รูปแบบที่พบบ่อยคือ soft delete — แทนที่จะลบ document คุณติดแฟล็กไว้บน document (ส่วนใหญ่พร้อม timestamp) ด้วยการ update แล้วกัน document ที่ถูกทำเครื่องหมายออกจากการอ่านปกติของคุณ

db.members.updateOne(
{ name: "Linus" },
{ $set: { deleted: true, deletedAt: new Date() } }
)

document ยังคงอยู่ตรงนั้น เพียงแค่ถูกทำเครื่องหมายไว้ — การอ่านปกติแค่กรองออกไป และคุณกู้คืนทีหลังได้ด้วยการ unset แฟล็กนั้น:

{
"_id": "65f0b3d4e4b0a1c2e4b0a1d1",
"name": "Linus",
"joined": 2023,
"deleted": true,
"deletedAt": "2026-06-25T10:15:00.000Z"
}
  • deleteMany ด้วย filter ที่ว่างเปล่า — ตรงกับทุก document — ล้าง collection ทั้งก้อน นี่คือการเรียก CRUD ที่อันตรายที่สุด ให้พิมพ์ filter ก่อนแล้วค่อยพิมพ์ method ทีหลัง จะได้ไม่เผลอรันแบบ filter ว่าง
  • การลบเป็นแบบถาวร ไม่เหมือนการ update ตรงที่ไม่มีสำเนา “ก่อนหน้า” หลงเหลือไว้ เว้นแต่คุณจะจัดเตรียมไว้ เมื่อไม่แน่ใจ ให้รัน filter เดียวกันผ่าน find ก่อน เพื่อดูให้ชัดว่าคุณกำลังจะลบอะไร
  • deleteOne คือราวกันตกของคุณสำหรับ “ลบระเบียนหนึ่งรายการที่เจาะจง” ต่อให้ filter หลวมกว่าที่คิด ก็ยังลบได้แค่ document เดียวอยู่ดี
  • soft delete แลกความเรียบง่ายเพื่อความปลอดภัย ทุก query ปกติต้องจำที่จะกัน document ที่ถูกทำเครื่องหมายออก แต่คุณได้ undo และเส้นทางการตรวจสอบมาแทน
ตัวเลือกBenefitCost
hard delete (deleteOne/deleteMany)คืนพื้นที่จัดเก็บทันที ไม่มีขยะตกค้าง เรียบง่ายที่สุดลบถาวร ไม่มี audit trail ไม่มี undo
soft delete (update flag เช่น deleted: true)กู้คืนได้ เก็บ audit trail และ historical data ไว้ครบต้องกรอง flag ใน query ทุกจุดที่อ่านข้อมูล ไม่งั้น document ที่ “ลบ” แล้วโผล่มาอีก และ collection โตขึ้นเรื่อย ๆ
  • ใช้ deleteOne เมื่อจริง ๆ ต้องการลบทุกรายการที่ตรง filter — เผลอใช้ deleteOne คิดว่าลบหมดแล้ว ทั้งที่จริง ๆ หยุดแค่ document แรก เหลือ document อื่นที่ตรง filter เดิมค้างอยู่ใน collection
  • ไม่ตรวจ deletedCount หลังลบ — สมมติว่าลบสำเร็จเพราะไม่มี error โยนขึ้นมา ทั้งที่ deletedCount อาจเป็น 0 เพราะ filter สะกดผิดหรือพิมพ์ field ผิด
  • ทำ soft delete แล้วลืมปรับ query ปกติให้กัน document ที่ถูกทำเครื่องหมายออก — เขียน find ปกติโดยไม่เติมเงื่อนไข deleted: { $ne: true } ทำให้ข้อมูลที่ “ลบ” ไปแล้วโผล่กลับมาในผลการค้นหาให้ผู้ใช้เห็น

💡 ตัวอย่างจากของจริง

ตะกร้าสินค้าที่ค้างอยู่ (abandoned cart) — job รายวันเรียก deleteMany({ status: "cart", updatedAt: { $lt: thirtyDaysAgo } }) กวาดตะกร้าที่ไม่มีการเคลื่อนไหวเกิน 30 วันทิ้ง เพื่อไม่ให้ collection บวมด้วยข้อมูลที่ไม่มีใครจะ checkout แล้ว

บัญชีผู้ใช้ (user account) — เมื่อผู้ใช้กด “ลบบัญชี” ระบบไม่เรียก deleteOne ทันที แต่ทำ soft delete ด้วย updateOne ตั้ง deleted: true และ deletedAt เพื่อให้มีช่วงเวลากู้คืนได้ (เช่น 30 วัน) ก่อนที่ job เบื้องหลังจะ deleteOne จริงเมื่อพ้นกำหนด

deleteMany ด้วย filter ที่ว่างเปล่าทำอะไร?
filter ของ deleteOne ไม่ตรงกับ document ใดเลย มีอะไรถูกรายงาน?
แก่นความคิดของ soft delete คืออะไร?