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

CRUD ใน MongoDB

ทุกฐานข้อมูลสรุปลงมาเหลือสี่สิ่งที่คุณทำกับข้อมูลได้ คือ ใส่ระเบียนใหม่เข้าไป อ่านกลับออกมา แก้ไข และลบทิ้ง สี่กริยานี้ย่อรวมกันเป็น CRUD — Create, Read, Update, Delete

ใน MongoDB “ระเบียน” เหล่านั้นคือ document หน้าตาคล้าย JSON ที่อยู่ใน collection และกริยาทั้งสี่จับคู่กับชุด method เล็ก ๆ ที่คาดเดาได้ หน้าตาแทบไม่ต่างกันเลย ไม่ว่าคุณจะพิมพ์ในเชลล์ หรือเรียกผ่าน driver ของ Node.js, Python, Go หรือ Rust

โมดูลนี้คือหัวใจของคอร์ส เมื่อจบโมดูลคุณจะโหลด document เข้า collection ดึงกลับออกมาด้วย filter แก้ field อย่างแม่นยำโดยไม่พังส่วนที่เหลือของ document ลบเฉพาะสิ่งที่ตั้งใจจะลบ และรวมการเขียนหลายครั้งให้เหลือ round trip เดียวได้ ข้อมูลตัวอย่างเราจงใจให้เล็กและเป็นเรื่องสมมติ — สมาชิกห้องสมุดไม่กี่คนกับหนังสือที่ยืมไป — เพื่อให้โฟกัสอยู่ที่ตัว operation

flowchart LR
  App["Your app or mongosh"] -->|"insert / find / update / delete"| Driver["Driver or shell"]
  Driver --> Coll["Collection: members"]
  Coll --> D1["{ _id, name, fines }"]
  Coll --> D2["{ _id, name, fines }"]
  Coll --> D3["{ _id, name, fines }"]
การเรียก CRUD วิ่งจากโค้ดของคุณ ผ่าน driver ไปยัง collection ของ document

แต่ละกลุ่มมี method แบบเอกพจน์ที่แตะ document ได้มากที่สุดหนึ่งรายการ และ method แบบพหูพจน์ที่แตะได้หลายรายการ ทั้งหมดนี้คือคำศัพท์ที่คุณจะได้ใช้ตลอดทั้งโมดูล:

  • CreateinsertOne เพิ่ม document ทีละรายการ, insertMany เพิ่มทีละ array
  • ReadfindOne คืน document แรกที่ตรงกัน (หรือไม่คืนอะไรเลย), find คืน cursor ครอบทุกรายการที่ตรงกัน
  • UpdateupdateOne เปลี่ยนรายการแรกที่ตรงกัน, updateMany เปลี่ยนทุกรายการที่ตรงกัน ทั้งคู่ใช้ update operator อย่าง $set และ $inc
  • DeletedeleteOne ลบรายการแรกที่ตรงกัน, deleteMany ลบทุกรายการที่ตรงกัน

รูปแบบเดียวกันนี้ปรากฏในทั้งห้าแท็บที่คุณจะเห็นตลอดโมดูล สังเกตว่าโค้ด insert อ่านแล้วแทบเหมือนกันหมด ทั้งในเชลล์และในทุก driver:

db.members.insertOne({ name: "Ada", fines: 0 })

หลัง insert เสร็จ document ที่เก็บอยู่จะหน้าตาแบบนี้ — สังเกต _id ที่ MongoDB เติมให้:

{ "_id": "65f0a1c2e4b0a1c2e4b0a1c2", "name": "Ada", "fines": 0 }

ใน Compass: ทั้งสี่กลุ่มนี้จับคู่กับ GUI ตรง ๆ — Compass แสดง document ในแท็บ Documents คุณ insert ได้ด้วยปุ่มสีเขียว ADD DATA แก้ field แบบ inline ได้ และลบ document ด้วยไอคอนถังขยะที่โผล่มาตอนเอาเมาส์ไปวาง

บทเรียนถัดไปจะหยิบแต่ละกลุ่มมาทีละกลุ่ม แล้วปิดท้ายด้วยสองท่าไม้ตายที่รวมหลายกลุ่มเข้าด้วยกัน:

  1. Insert — การเพิ่ม document ทีละรายการหรือทีละหลายรายการ และ _id อัตโนมัติ
  2. Readfind เทียบกับ findOne, filter แบบ equality และแบบหลาย field พร้อมแตะ projection เป็นครั้งแรก
  3. Update$set, $inc, $unset, $push, $pull และการอ่านจำนวนที่ตรงกัน (match count) ที่การเขียนคืนกลับมา
  4. Delete — การลบ document อย่างแม่นยำ บวกกับรูปแบบ soft-delete
  5. Upsert และ bulk write — “insert ถ้ายังไม่มี ไม่อย่างนั้นก็ update” และการยัดหลาย operation ให้เหลือ round trip เดียวไป server
  • method แบบเอกพจน์ (insertOne, findOne, updateOne, deleteOne) ไม่มีทางกระทบ document เกินหนึ่งรายการ แม้จะมีหลายรายการตรงกัน — ใช้เป็นราวกันตกได้ดี
  • method แบบเขียนจะคืน acknowledgement ที่บอกว่าเกิดอะไรขึ้น (จำนวนนับ, id ที่ insert เข้าไป) อ่านผลลัพธ์ตรงนี้คือวิธียืนยันว่า operation ทำตามที่คุณตั้งใจจริง
  • filter ที่ไม่ตรงกับอะไรเลยไม่ถือเป็น error ถ้า update หรือ delete ไม่ตรงกับ document สักรายการ ผลลัพธ์ก็แค่รายงานว่าเปลี่ยนไป 0 รายการ
ตัวเลือกBenefitCost
method แบบเอกพจน์ (insertOne/updateOne/deleteOne)ควบคุมแม่นยำ กระทบ document เดียว คาดเดาผลได้ง่ายต้องเรียกซ้ำหลายครั้งถ้าต้องจัดการหลาย document ทำให้ round trip บ่อย
method แบบพหูพจน์ (insertMany/updateMany/deleteMany)จัดการหลาย document ในการเรียกเดียว เร็วกว่ามากfilter หรือ array ที่ผิดพลาดกระทบวงกว้างกว่าที่ตั้งใจ
hard deleteคืนพื้นที่จัดเก็บทันที เรียบง่ายลบถาวร ไม่มี audit trail
soft delete (update flag)กู้คืนได้ มี audit trailต้องกรอง flag ในทุก query ต่อจากนี้
  • เขียนโค้ด insert วนลูปทีละ document — ควรใช้ insertMany ส่งเป็น batch เดียว ลดจำนวน round trip ไป server
  • เรียก updateMany/deleteMany โดยไม่ตรวจ filter ให้ดีก่อน — ลืมใส่ filter หรือใส่ filter กว้างเกินไป ทำให้กระทบ document ทั้ง collection โดยไม่ตั้งใจ
  • ไม่ตรวจผลลัพธ์ (acknowledgement) หลังเขียน — เห็นแค่ว่าไม่มี error โยนขึ้นมาแล้วสรุปว่าสำเร็จ ทั้งที่ matchedCount/modifiedCount/deletedCount อาจเป็น 0

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

ระบบสั่งซื้อ e-commerce — เมื่อลูกค้ากด checkout ระบบ insertOne order ใหม่ ตามด้วย updateMany ลด stock ของสินค้าในตะกร้าทุกชิ้น แล้วสุดท้าย deleteMany ล้างตะกร้าที่ค้างอยู่เกิน 30 วัน ทั้งหมดคือ CRUD สี่กลุ่มทำงานร่วมกันใน pipeline เดียว

ระบบ session ของเว็บแอป — ใช้ findOne ตรวจ session ทุก request, updateOne ต่ออายุ TTL, และ deleteOne เมื่อ logout

method ใดอ่าน document แรกที่ตรงกับ filter กลับคืนมา?
รูปแบบเอกพจน์ของ method แบบเขียน (เช่น updateOne) รับประกันอะไร?
CRUD ย่อมาจากการทำงานสี่อย่างใด?