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

Document เทียบกับ relational

ถ้าคุณรู้จัก relational database อยู่แล้ว วิธีที่เร็วที่สุดในการคล่อง MongoDB คือการแปลคำที่คุณรู้ให้เป็นคำที่ MongoDB ใช้ แนวคิดส่วนใหญ่เรียงตรงกัน มีสองสามอันที่ต่างออกไปในแบบที่เป็นแก่นของโมเดล document บทเรียนนี้คือตารางแปลนั้น บวกกับการมองครั้งแรกไปที่คำถามที่โมดูล Data Modeling ตอบไว้อย่างครบถ้วน: เมื่อไรควร embed ข้อมูลที่เกี่ยวข้อง และเมื่อไรควรเก็บไว้ใน collection แยกต่างหาก

ฝั่งซ้ายคือโลก relational ฝั่งขวาคือของที่เทียบเคียงกันใน MongoDB:

  • Database → Database อันนี้คำเดียวกันและแนวคิดเดียวกัน: ภาชนะที่มีชื่อสำหรับบรรจุ collection
  • Table → Collection collection เก็บ document แบบเดียวกับที่ table เก็บ row แต่ไม่ได้บังคับให้ทุก document มี column เหมือนกันเป๊ะ
  • Row → Document document คือหนึ่งระเบียน ต่างจาก row ตรงที่ซ้อน array และ sub-object ได้ ไม่ใช่แค่แถวของ cell แบน ๆ
  • Column → Field field คือคู่ key-value ภายใน document field เป็นแบบต่อ document ดังนั้นสอง document ใน collection ไม่จำเป็นต้องมี field ชุดเดียวกัน
  • JOIN → Embedding หรือ $lookup ความสัมพันธ์ถูกจัดการด้วยการ embed ข้อมูลที่เกี่ยวข้องไว้ภายใน document เดียว หรือลิงก์ document ไว้แล้วค่อยรวมตอนอ่านด้วย aggregation stage $lookup
  • Schema → Flexible schema (พร้อม validation ที่เป็น optional) รูปร่างของข้อมูลคุณยืดหยุ่นโดยค่าเริ่มต้น และคุณสามารถเพิ่มกฎ validation เมื่อต้องการให้ database บังคับโครงสร้าง
  • Primary key → _id ทุก document มี _id ที่ไม่ซ้ำกัน บทบาทเดียวกับที่ primary key เล่นให้กับ row
flowchart LR
  subgraph SQL["Relational"]
    direction TB
    R1["Database"]
    R2["Table"]
    R3["Row"]
    R4["Column"]
    R5["JOIN"]
  end
  subgraph MDB["MongoDB"]
    direction TB
    M1["Database"]
    M2["Collection"]
    M3["Document"]
    M4["Field"]
    M5["Embedding or lookup"]
  end
  R1 --> M1
  R2 --> M2
  R3 --> M3
  R4 --> M4
  R5 --> M5
แนวคิดฝั่ง relational เทียบกับของที่ตรงกันใน MongoDB

ใน relational database คุณเกือบจะ normalise เสมอ: สมาชิกอยู่ในตาราง members และการยืมของพวกเขาอยู่ในตาราง loans ที่ join กันด้วย key คุณประกอบภาพเต็มด้วย JOIN ทุกครั้งที่ query

MongoDB ให้ทางเลือกแก่คุณ คุณสามารถ embed ข้อมูลที่เกี่ยวข้องไว้ภายใน document พ่อแม่โดยตรง ดังนั้นการอ่านครั้งเดียวจึงคืนทุกอย่าง:

{
"_id": "65f0b3d4e4b0a1c2e4b0a1d1",
"name": "Ada",
"borrowed": [
{ "title": "Compilers", "due": "2026-07-01" },
{ "title": "Algorithms", "due": "2026-07-15" }
]
}

หรือคุณสามารถเก็บข้อมูลไว้ใน collection แยกต่างหาก แล้วลิงก์ด้วย id จากนั้นค่อยรวมตอน query ด้วย $lookup — การ join ตอนอ่านของ MongoDB:

{ "_id": "65f0b3d4e4b0a1c2e4b0a1d1", "name": "Ada" }
{ "_id": "a1", "member_id": "65f0b3d4e4b0a1c2e4b0a1d1", "title": "Compilers", "due": "2026-07-01" }

การลงรายละเอียดเต็มคือโมดูล Data Modeling แต่สัญชาตญาณที่ต้องสร้างตอนนี้คือ:

  • Embed เมื่อ ข้อมูลที่เกี่ยวข้องเป็นของพ่อแม่อันเดียว ถูกอ่านไปพร้อมกัน และไม่โตแบบไร้ขีดจำกัด หนังสือที่สมาชิกยืม, รายการสินค้าในออเดอร์, ความคิดเห็นล่าสุดของโพสต์ — เหล่านี้อ่านเป็น document เดียวในการเดินทางครั้งเดียว
  • เก็บแยกเมื่อ ข้อมูลถูกแชร์ระหว่างพ่อแม่หลายตัว ถูกอ้างอิงอย่างอิสระ หรือเติบโตใหญ่ แคตตาล็อกหนังสือที่สมาชิกหลายคนยืมควรเป็น collection ของตัวเอง อ้างอิงด้วย id ไม่ใช่คัดลอกเข้าไปในทุกสมาชิก

คำถามนำทางคือ ฉันอ่านข้อมูลนี้อย่างไร? ถ้าคุณเกือบจะต้องการพ่อแม่และลูกพร้อมกันเสมอ การ embed ก็คุ้มค่า ถ้าลูกมีชีวิตเป็นของตัวเอง ก็แยกออกไปแล้วลิงก์

  • อย่า normalise โดยอัตโนมัติ เมื่อมาจาก SQL แรงกระตุ้นคือการแยกทุกอย่างออกเป็น collection ที่ต่างกัน ใน MongoDB ข้อมูลที่ถูกอ่านพร้อมกันมักจะควรอยู่ด้วยกัน ใน document เดียว
  • collection ไม่ใช่ table เพราะไม่ได้บังคับชุด column ตายตัว สอง document สามารถพก field ต่างกันได้ และนั่นเป็นสิ่งที่ออกแบบให้อนุญาต
  • $lookup มีให้ใช้ แต่ไม่ใช่ join ฟรี ๆ การ embed โดยทั่วไปเร็วกว่าสำหรับข้อมูลที่คุณอ่านพร้อมกัน; เอื้อมไปหา $lookup เมื่อข้อมูลควรอยู่ใน collection แยกต่างหากจริง ๆ
  • _id คือ primary key ของคุณแบบฟรี ๆ คุณได้ identifier ที่ไม่ซ้ำและถูก index บนทุก document โดยไม่ต้องประกาศ
ตัวเลือกBenefitCost
Denormalized document model (embed)อ่าน document เดียวได้ข้อมูลครบ ไม่ต้อง join เร็วสำหรับ workload ที่อ่านพ่อแม่กับลูกพร้อมกันเสมอข้อมูลที่ซ้ำกันในหลาย document ต้องอัปเดตหลายจุดถ้าเปลี่ยน และ document อาจโตจนถึงขีดจำกัดถ้า array ไม่มีขอบเขต
Normalized relational model (foreign key)ไม่มีข้อมูลซ้ำ แก้ที่เดียวจบ และ foreign key รับประกัน referential integrity ให้โดย databaseต้อง join หลายตารางแทบทุก query ซึ่งมีต้นทุนเพิ่มขึ้นตามจำนวนตารางที่เกี่ยวข้อง
  • สมมติว่า $lookup ให้การรับประกันเหมือน SQL JOIN ทุกอย่าง$lookup ไม่มี referential integrity แบบ foreign key constraint ถ้า document ที่ถูกอ้างอิงถูกลบไป $lookup จะแค่คืน array ว่างเปล่า ไม่มี error เตือน
  • Embed ทุกอย่างเพราะเชื่อว่า embed เร็วกว่าเสมอ — ถ้าข้อมูลลูกโตแบบไม่มีขอบเขต (เช่น log ของกิจกรรมที่เพิ่มไม่หยุด) การ embed ในที่สุดจะชน document size limit และทำให้ document แม่โตจนอ่าน/เขียนช้าลง ควรเก็บแยกตั้งแต่ต้น
  • คิดว่าการเรียง collection ตาม _id เท่ากับการเรียงตาม field เวลาที่แน่นอน — ObjectId เรียงตามเวลาสร้างแค่ระดับวินาที ไม่ใช่ timestamp ที่แม่นยำระดับมิลลิวินาทีหรือรับประกัน order เป๊ะข้ามหลาย process ถ้าต้องการลำดับที่แน่นอนต้องมี field เวลาของตัวเอง

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

MetLife — รวมข้อมูลลูกค้าจากระบบ relational เดิมหลายสิบระบบเข้าเป็น document เดียวต่อลูกค้าหนึ่งคน (single customer view) แทนที่จะพยายาม join ข้าม database ที่แยกกันอยู่คนละระบบ

ระบบ e-commerce ทั่วไป — มักจะ embed รายการสินค้าในออเดอร์ไว้ใน document ของออเดอร์เอง (denormalize) เพราะแทบทุกครั้งที่อ่านออเดอร์ต้องเห็นรายการสินค้าพร้อมกันเสมอ แต่เก็บแคตตาล็อกสินค้าไว้ใน collection แยกเพราะสินค้าหนึ่งตัวถูกอ้างอิงจากหลายออเดอร์

อะไรคือสิ่งที่เทียบเท่ากับ table ของ relational ใน MongoDB?
ใน MongoDB จัดการความสัมพันธ์ระหว่างระเบียนอย่างไร?
เมื่อไรที่การ embed มักเป็นทางเลือกที่ดีกว่าการแยก collection?