Document เทียบกับ relational
ถ้าคุณรู้จัก relational database อยู่แล้ว วิธีที่เร็วที่สุดในการคล่อง MongoDB คือการแปลคำที่คุณรู้ให้เป็นคำที่ MongoDB ใช้ แนวคิดส่วนใหญ่เรียงตรงกัน มีสองสามอันที่ต่างออกไปในแบบที่เป็นแก่นของโมเดล document บทเรียนนี้คือตารางแปลนั้น บวกกับการมองครั้งแรกไปที่คำถามที่โมดูล Data Modeling ตอบไว้อย่างครบถ้วน: เมื่อไรควร embed ข้อมูลที่เกี่ยวข้อง และเมื่อไรควรเก็บไว้ใน collection แยกต่างหาก
คำศัพท์ที่ map แล้ว
หัวข้อที่มีชื่อว่า “คำศัพท์ที่ map แล้ว”ฝั่งซ้ายคือโลก 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 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" }เมื่อไรควร embed เทียบกับเก็บแยก
หัวข้อที่มีชื่อว่า “เมื่อไรควร embed เทียบกับเก็บแยก”การลงรายละเอียดเต็มคือโมดูล 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 โดยไม่ต้องประกาศ
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| 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 แยกเพราะสินค้าหนึ่งตัวถูกอ้างอิงจากหลายออเดอร์