MongoDB คืออะไร
MongoDB เป็น document database วลีเดียวนี้บรรจุแนวคิดทั้งหมดไว้แล้ว: สิ่งที่เก็บคือ document ที่เป็น object คล้าย JSON ที่ครบในตัวเอง และกลุ่มของ document ก็คือ collection ฝั่ง relational database ขอให้คุณกระจายหนึ่งระเบียนเชิงตรรกะออกไปยังหลายตารางแล้วเย็บกลับมาด้วย join ส่วน MongoDB ปล่อยให้หนึ่งระเบียนเป็น document เดียวที่บรรจุทุกอย่างที่ควรอยู่ด้วยกันไว้แล้ว — รวมทั้ง object ซ้อนในและ array
ผลในทางปฏิบัติคือข้อมูลใน database มักจะมีหน้าตาเหมือน object ในโปรแกรมของคุณ สมาชิกที่มีชื่อ ยอดค่าปรับ และรายการหนังสือที่ยืม คือ document เดียว ไม่ใช่ row ในตาราง members บวกกับ row ในตาราง loans ที่คุณต้อง join ทุกครั้งที่ต้องการเห็นภาพเต็ม
document แทน row
หัวข้อที่มีชื่อว่า “document แทน row”ในโลก relational สมาชิกและหนังสือที่พวกเขายืมอยู่ในสองตารางที่เชื่อมกันด้วย key ใน MongoDB ข้อมูลเดียวกันนี้เป็น document เดียวที่มี array ฝังอยู่
flowchart LR
subgraph RDBMS["Relational: two tables joined by a key"]
direction TB
T1["members table
id | name | fines"]
T2["loans table
id | member_id | title"]
T1 -->|"member_id = id"| T2
end
subgraph MONGO["MongoDB: one document"]
direction TB
D["members collection
one document:
name, fines, borrowed[ ... ]"]
end
RDBMS -.->|"same data, different shape"| MONGO นี่คือ document นั้น รันจริง fence นี้ insert สมาชิก อ่านกลับมา และถามเซิร์ฟเวอร์ว่ามันคือใคร — output ใต้แต่ละ fence คือสิ่งที่ mongosh 2.11.1 พิมพ์บน Mac เครื่องนี้กับ mongo:8.3.11 (single-node replica set) ไม่ใช่ JSON ที่เขียนเอง:
print(db.version()); // server versionprint(db.hello().isWritablePrimary); // this node accepts writesprint(db.hello().maxBsonObjectSize); // the per-document ceiling, in bytes
db.members.insertOne({ name: "Grace", fines: 0, borrowed: ["Compilers", "Algorithms"] });const grace = db.members.findOne({ name: "Grace" });console.log(grace);
assert.strictEqual(grace.borrowed.length, 2); // the array lives INSIDE the document: no loans table, no joinassert.strictEqual(db.hello().maxBsonObjectSize, 16777216);8.3.11true16777216{ _id: ObjectId('6abca2af5967435115bd62b0'), name: 'Grace', fines: 0, borrowed: [ 'Compilers', 'Algorithms' ]}สองอย่างที่ควรสังเกต _id คือ ObjectId('...') จริง (ชนิด BSON ไม่ใช่ string อย่างที่ JSON ธรรมดาให้) — บทถัด ๆ ไปจะแกะให้ดู — และ maxBsonObjectSize คือ 16777216 byte: 16 MiB คือเพดานตายตัวของหนึ่ง document ซึ่งเป็นเหตุผลที่ “embed ทุกอย่าง” มีขีดจำกัด
schema ที่ยืดหยุ่น
หัวข้อที่มีชื่อว่า “schema ที่ยืดหยุ่น”collection ไม่ได้บังคับให้ทุก document มี field ชุดเดียวกัน document ของสมาชิกคนหนึ่งอาจมี email, อีกคนอาจไม่มี, และคนที่สามอาจเพิ่ม phone ที่ไม่มีใครอื่นมี MongoDB ไม่ปฏิเสธตัวที่แตกต่างนี้ สิ่งนี้เรียกว่า schema แบบ flexible (หรือ dynamic) และเป็นฟีเจอร์ที่คนสังเกตเห็นเป็นอันดับแรก
ความยืดหยุ่นเป็นความสะดวก ไม่ใช่ข้ออ้างสำหรับความวุ่นวาย ในทางปฏิบัติคุณจงใจทำให้ document ใน collection ค่อนข้างสม่ำเสมอ และ MongoDB ยอมให้คุณเพิ่มกฎ schema validation เมื่อคุณต้องการให้ database บังคับรูปร่าง ประเด็นคือ schema อยู่ในมือคุณและสามารถวิวัฒน์ทีละ field โดยไม่ต้อง migration ที่เขียนทับทุกระเบียนที่มีอยู่ตั้งแต่ต้น
db.people.insertOne({ name: "Ada", fines: 0 });console.log(db.people.find({}, { _id: 0 }).toArray());assert.strictEqual(db.people.countDocuments({ email: { $exists: true } }), 1);[ { name: 'Ada', fines: 0 }, { name: 'Alan', fines: 2, email: '[email protected]' }]ทั้งสองนี้เป็นสมาชิกที่ถูกต้องสมบูรณ์ใน collection เดียวกัน แม้ว่าจะมีเพียงหนึ่งที่มี email
เมื่อไร MongoDB เหมาะสม
หัวข้อที่มีชื่อว่า “เมื่อไร MongoDB เหมาะสม”MongoDB มักจะเป็นทางเลือกที่สบายใจเมื่อ:
- ข้อมูลของคุณเป็นลำดับชั้นหรือหลากหลายโดยธรรมชาติ document, โปรไฟล์, แคตตาล็อกสินค้า, event, และเนื้อหาที่ระเบียนไม่ได้มี field ชุดเดียวกันทั้งหมด map ลงบน document ได้อย่างสะอาด
- คุณอ่านและเขียน object ทั้งก้อนพร้อมกัน ถ้าแอปของคุณเกือบจะต้องการทั้งสมาชิก และ หนังสือที่ยืมพร้อมกันเสมอ การ embed ไว้ใน document เดียวหมายถึงการอ่านครั้งเดียวที่เร็วแทนที่จะเป็น join
- schema ของคุณยังเปลี่ยนแปลงอยู่ ผลิตภัณฑ์ในระยะเริ่มต้นเปลี่ยนรูปร่างตลอดเวลา schema ที่ยืดหยุ่นยอมให้คุณเพิ่ม field วันนี้โดยไม่ต้อง migration
- คุณต้องสเกลการเขียนแบบแนวนอน MongoDB shard ข้ามหลายเครื่อง ซึ่งโมดูล Operations & Scaling ในภายหลังจะครอบคลุม
เมื่อไร relational database เป็นเครื่องมือที่ดีกว่า
หัวข้อที่มีชื่อว่า “เมื่อไร relational database เป็นเครื่องมือที่ดีกว่า”document database ไม่ใช่คำตอบที่ถูกต้องเสมอไป เลือกใช้ relational database (PostgreSQL, MySQL และพวกพ้อง) เมื่อ:
- ข้อมูลของคุณเป็น relational สูงและสม่ำเสมอ ความสัมพันธ์ many-to-many จำนวนมากระหว่าง entity ที่สำคัญเท่า ๆ กัน คือสิ่งที่ SQL join ถูกสร้างมาเพื่อพอดี
- คุณต้องการ transaction หลาย row เป็นเรื่องประจำวัน MongoDB รองรับ transaction หลาย document แต่บัญชีแยกประเภทแบบธนาคารที่เกือบทุก operation ครอบคลุมหลายระเบียน มักจะเข้ากับโมเดล relational ได้อย่างเป็นธรรมชาติมากกว่า
- คุณพึ่งพา ad-hoc SQL ที่ทรงพลังและระบบนิเวศรายงานที่เติบโตเต็มที่ เครื่องมือ SQL, การผสานรวม BI, และความคุ้นเคยของนักวิเคราะห์ที่สั่งสมมาหลายทศวรรษ เป็นข้อได้เปรียบจริง
- schema ที่เข้มงวดและบังคับตั้งแต่ต้นเป็นฟีเจอร์ที่คุณต้องการ ไม่ใช่ข้อจำกัดที่คุณพยายามหนีออกมา
สรุปอย่างตรงไปตรงมา: เลือก database ที่รูปร่างตามธรรมชาติตรงกับข้อมูลและ access pattern ของคุณ MongoDB เปล่งประกายเมื่อระเบียนเป็น object ที่ครบในตัวเองที่คุณอ่านและเขียนเป็นหน่วยเดียว
เคล็ดลับและข้อควรระวัง
หัวข้อที่มีชื่อว่า “เคล็ดลับและข้อควรระวัง”- “flexible schema” ไม่ได้หมายความว่า “ไม่มี schema” แต่หมายความว่า schema เป็นความรับผิดชอบของคุณและสามารถเปลี่ยนแปลงทีละน้อยได้ collection ที่แข็งแรงส่วนใหญ่ค่อนข้างสม่ำเสมอในทางปฏิบัติ
- MongoDB ไม่ใช่ key-value store และ ไม่ใช่ JSON ที่ไม่มี schema ทิ้งไว้บนดิสก์ MongoDB ทำ index ให้ field รัน query ที่ทรงพลังได้ และตรวจรูปร่างข้อมูลให้เมื่อคุณสั่ง
- ทางเลือกนี้แทบไม่เคยเป็นแบบทั้งหมดหรือไม่เลย หลายระบบใช้ MongoDB สำหรับ workload บางอย่างและ relational database สำหรับอย่างอื่น
อ่านโค้ดที่ AI สร้าง
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI สร้าง”ผู้ช่วย AI ที่ถูกขอ “collection members ใน MongoDB” มักจะปลอบใจว่า database นี้ schemaless จึงไม่ต้องประกาศอะไร โค้ดดูไม่มีพิษภัย:
🐛 หา bug ในโค้ดนี้
// AI comment: "MongoDB is schemaless, so we can just insert - no setup needed"db.members.insertMany([{ name: "Ada", fines: 0 },{ name: "Alan", fines: 2 },]);db.members.insertOne({ name: "Grace", finess: 9 }); // typo in the field name
// later: who owes money?db.members.countDocuments({ fines: { $gte: 1 } });เฉลย
insert นี้ ถูกรับ — MongoDB ไม่มี schema ให้ปฏิเสธ Grace ค้างชำระ 9 แต่ document ของเธอมี field สะกดผิดคือ finess ดังนั้นจำนวนที่นับได้คือ 1 (เฉพาะ Alan) แทนที่จะเป็น 2 และไม่มี error ใด ๆ เกิดขึ้น find({ fines: { $exists: false } }) จะเปิดโปงเธอ: [ { name: 'Grace', finess: 9 } ] “ยืดหยุ่น” หมายความว่ารูปร่างเป็นความรับผิดชอบของ คุณ วิธีแก้คือให้ database บังคับรูปร่างด้วย validator แบบ $jsonSchema (พิสูจน์ด้านล่าง; บท schema-validation ลงลึกอีกที):
db.createCollection("checked", { validator: { $jsonSchema: { required: ["name", "fines"], properties: { _id: {}, name: { bsonType: "string" }, fines: { bsonType: "int" } }, additionalProperties: false, // without `_id: {}` above, this would reject EVERY insert } },});let rejected = "";try { db.checked.insertOne({ name: "Grace", finess: 9 }); } catch (e) { rejected = e.message; print(rejected); }assert.strictEqual(rejected, "Document failed validation");assert.strictEqual(db.checked.insertOne({ name: "Grace", fines: 9 }).acknowledged, true);Document failed validationตอนนี้ typo กลายเป็น error ตอนเขียน (errInfo.details.schemaRulesNotSatisfied แสดง additionalProperties และ required) สังเกตบรรทัด _id: {}: เมื่อใช้ additionalProperties: false เซิร์ฟเวอร์ถือว่า _id เป็น property เกินด้วย — กับดักจริงที่เจอตอนเขียนบทนี้
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”คิดว่า collection คือ ชั้นวางแฟ้มที่อธิบายตัวเองได้ ไม่ใช่ตาราง แต่ละแฟ้ม (document) พกชื่อ field ของตัวเองมาด้วย จึงไม่มีอะไรนอกแฟ้มที่ต้องเห็นด้วยกับมัน นั่นคือเหตุผลที่การเพิ่ม field ไม่เสียอะไร — และเป็นเหตุผลที่ typo ก็เป็นแค่อีก field ที่ถูกต้อง database ให้ การเก็บและ query เป็นค่าเริ่มต้น และ การบังคับรูปร่างเฉพาะเมื่อคุณขอ
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Document database (MongoDB) | รูปร่างข้อมูลยืดหยุ่น เหมาะกับข้อมูลที่เป็นลำดับชั้นหรือหลากหลายโดยธรรมชาติ อ่านและเขียน object ทั้งก้อนโดยไม่ต้อง join | schema ไม่ถูกบังคับโดยอัตโนมัติ (typo finess ด้านบนถูกรับ) — ความสม่ำเสมอเป็นเรื่องของทีม และ ad-hoc join ข้าม collection ทำได้จำกัดกว่า SQL |
| Relational database (PostgreSQL, MySQL) | schema เข้มงวดตั้งแต่ต้น รองรับ join แบบ many-to-many ที่ซับซ้อนได้ดี และมี tooling/ระบบนิเวศรายงานที่เติบโตเต็มที่ | การเปลี่ยน schema ต้อง migration ที่กระทบทุก row ที่มีอยู่ และข้อมูลที่เกี่ยวข้องกันกระจายไปหลายตารางแล้วต้อง join กลับ |
MongoDB พร้อม $jsonSchema validation | ยืดหยุ่นตรงที่ต้องการ เข้มงวดตรงที่สำคัญ | คุณต้องดูแล schema และ migration เอง คล้ายฝั่ง relational |
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “flexible schema” แปลว่า “ไม่มี schema” — ผิด schema อยู่ในโค้ดของคุณและ (ถ้าต้องการ) ใน validator ตัวอย่าง
finessด้านบนคือรูปแบบความล้มเหลวเมื่อไม่มีใครเป็นเจ้าของ - MongoDB คือ key-value store หรือ JSON ที่ทิ้งไว้บนดิสก์ — มันทำ index ให้ field รัน query ที่ทรงพลัง และตรวจรูปร่างข้อมูลเมื่อคุณสั่ง ค่า
_idเป็นObjectId(BSON) จริง ไม่ใช่ JSON string - เลือก MongoDB เพราะกระแส — join แบบ many-to-many ที่หนักและสม่ำเสมอมักเหมาะกับ relational database กว่า ให้เลือกตาม access pattern
- ระบบต้องเลือกอย่างใดอย่างหนึ่ง — หลายระบบใช้ MongoDB สำหรับส่วนที่ยืดหยุ่น และ relational database สำหรับส่วนที่ต้องการ transaction หลาย row
- “document ใหญ่แค่ไหนก็ได้” —
maxBsonObjectSizeพิมพ์16777216ด้านบน: 16 MiB ต่อ document
💡 ตัวอย่างจากของจริง
Forbes — ย้าย CMS ที่ให้บริการเนื้อหาทั้งหมดบน forbes.com ไปยัง MongoDB Atlas ในปี 2019; โครงสร้างข้อมูลที่ยืดหยุ่นทำให้ทีมเพิ่ม component ใหม่ได้เร็ว และ Forbes รายงานว่าเวลา build เฉลี่ยลดจาก 25 นาทีเหลือเก้านาที (mongodb.com/customers/forbes, เรียกอ่านเมื่อ 2026-09-30)
บทนี้ไม่ใช้ Python/Go/Rust; ทุก fence ด้านบนรันใน mongosh