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

การ insert document

การ insert คือ operation ที่ผ่อนคลายที่สุดใน MongoDB คุณยื่น document ให้ฐานข้อมูล ฐานข้อมูลเก็บไว้ แล้วตอบกลับมาว่าเก็บอะไรไป ไม่ต้องนิยาม schema ล่วงหน้า ไม่มีคอลัมน์ของตารางให้จัดให้ตรงกัน — ขอแค่ document เป็นข้อมูลแบบ JSON ที่ถูกต้องก็เข้าไปได้ อิสระตรงนี้แหละคือเหตุผลว่าทำไมวินัยเล็ก ๆ รอบ field _id ถึงคุ้มค่า เพราะเป็นงานเก็บบันทึกชิ้นเดียวที่ทุกการ insert ต้องยุ่งด้วย

เราจะสร้างห้องสมุดสมมติกันต่อ สมาชิกแต่ละคนคือ document หนึ่งรายการ และบทเรียนนี้เราจะเพิ่มสมาชิกเข้าไปใน collection ชื่อ members ทุก output ด้านล่างจับมาจาก mongo 8.3.11 กับ mongosh 2.11.1 และ Node driver 7.7.0 (แท็บ Python, Go และ Rust รันหนึ่งครั้งในโปรแกรม scratch บน server เดียวกัน); id และเวลาบนเครื่องของคุณจะต่างออกไป

insertOne รับ document เดียวพอดีแล้วเก็บไว้ ถ้าคุณไม่ใส่ _id MongoDB จะสร้างให้เป็น ObjectId — ค่าขนาด 12 ไบต์ที่ไม่ซ้ำกันทั้ง collection และเข้ารหัสเวลาที่สร้างไว้ด้วย id ที่สร้างนี้คือ primary key ของ document

mongosh/insert-one.js
const res = db.members.insertOne({ name: "Grace", joined: 2024, fines: 0 });
console.log(res);
console.log(db.members.findOne({ _id: res.insertedId }));
assert.strictEqual(res.acknowledged, true);

acknowledgement บอกว่า operation สำเร็จและส่ง id ที่ถูกกำหนดกลับมา สิ่งที่แต่ละแท็บพิมพ์จริง (ObjectId ถูกพิมพ์ต่างกันในแต่ละ driver — เป็น 12 ไบต์เดียวกัน):

mongosh { acknowledged: true, insertedId: ObjectId('6abca7761ca8500949f70bb3') }
Node { acknowledged: true, insertedId: new ObjectId('6abca7762e488e625dd180ed') }
Python 6abca733ec5eb1df4567945d
Go ObjectID("6abca735ba87132b4c842253")
Rust ObjectId("6abca7370cacf93463d138ad")

และ document ที่ตอนนี้อยู่ใน collection (findOne ใน shell) — _id เป็นสิ่งเดียวที่คุณไม่ได้พิมพ์เอง:

{
_id: ObjectId('6abca7761ca8500949f70bb3'),
name: 'Grace',
joined: 2024,
fines: 0
}

ใน Compass: เปิด collection members ใช้เมนู Add Data แล้วเลือก Insert Document (เอกสาร Compass เรียกอ่านเมื่อ 2026-09-30, mongodb.com/docs/compass/current/documents/insert); เลือกโหมด Shell Syntax, Field-by-Field หรือ JSON ถ้าไม่ระบุ _id Compass จะสร้าง ObjectId ให้

เมื่อมี document เป็นชุด insertMany ส่งทั้งหมดในการเดินทางครั้งเดียวแทนที่จะเรียกทีละ document คุณส่ง array เข้าไป และได้ id ของทุกอย่างที่ถูก insert กลับมา โดยอ้างด้วยตำแหน่งใน array

mongosh/insert-many.js
const res = db.members.insertMany([
{ name: "Linus", joined: 2023, fines: 2 },
{ name: "Margaret", joined: 2025, fines: 0 },
{ name: "Edsger", joined: 2022, fines: 5 }
]);
console.log(res);
assert.strictEqual(Object.keys(res.insertedIds).length, 3); // the shell result has no insertedCount

acknowledgement map ดัชนี array แต่ละตัวไปยัง id ที่สร้างให้ มีความต่างจริงสองข้อที่ควรรู้: ผลลัพธ์ใน shell ไม่มี insertedCount (ให้นับ key ของ insertedIds) ขณะที่ ผลลัพธ์ใน Node มี:

mongosh
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6abca7761ca8500949f70bb4'),
'1': ObjectId('6abca7761ca8500949f70bb5'),
'2': ObjectId('6abca7761ca8500949f70bb6')
}
}
Node
{
acknowledged: true,
insertedCount: 3,
insertedIds: {
'0': new ObjectId('6abca77646b3ce4be9c0507f'),
'1': new ObjectId('6abca77646b3ce4be9c05080'),
'2': new ObjectId('6abca77646b3ce4be9c05081')
}
}
Python, Go, Rust: 3 (the length of the ids list, the number we sent)

_id ต้องไม่ซ้ำ insert document ที่สองด้วย id ที่มีอยู่แล้วจะได้ error duplicate key code 11000 พร้อมค่าที่ชนกันใน message:

mongosh/insert-duplicate.js
db.members.drop();
const first = db.members.insertOne({ name: "Grace" });
let err;
try { db.members.insertOne({ _id: first.insertedId, name: "Impostor" }); } catch (e) { err = e; }
console.log(err.name, err.code);
console.log(err.message);
assert.strictEqual(err.code, 11000);
assert.strictEqual(db.members.countDocuments(), 1); // the original is untouched
MongoServerError 11000
E11000 duplicate key error collection: b1_run_insert.members index: _id_ dup key: { _id: ObjectId('6abca78db69a4386bb444676') }

driver Node โยน MongoServerError เดียวกันพร้อม code: 11000 และเปิดเผย key ที่ล้มเหลวเป็น err.keyValue ({ _id: new ObjectId('...') })

insertMany เป็น ordered โดยค่าเริ่มต้น: หยุดที่ document แรกที่ล้มเหลว ด้วย { ordered: false } มันพยายามทุก document แล้วรายงานความล้มเหลวตอนท้าย เราพิสูจน์ทั้งสองแบบด้วย batch เดียวกัน — _id: 2 มีอยู่แล้ว document กลางจากสามจึงชน:

mongosh/insert-ordered.js
db.members.drop();
db.members.insertOne({ _id: 2, name: "Existing" });
const batch = () => [{ _id: 1, name: "A" }, { _id: 2, name: "B (duplicate)" }, { _id: 3, name: "C" }];
let e1;
try { db.members.insertMany(batch()); } catch (e) { e1 = e; }
console.log("ordered :", e1.name, e1.code, "inserted", e1.result.insertedCount, "failed at index", e1.writeErrors[0].index);
console.log(db.members.find({}, { name: 1 }).sort({ _id: 1 }).toArray());
db.members.deleteMany({ _id: { $ne: 2 } });
let e2;
try { db.members.insertMany(batch(), { ordered: false }); } catch (e) { e2 = e; }
console.log("unordered:", e2.name, e2.code, "inserted", e2.result.insertedCount, "failed at index", e2.writeErrors.map((w) => w.index));
console.log(db.members.find({}, { name: 1 }).sort({ _id: 1 }).toArray());
assert.strictEqual(e1.result.insertedCount, 1);
assert.strictEqual(e2.result.insertedCount, 2);
ordered : MongoBulkWriteError 11000 inserted 1 failed at index 1
[ { _id: 1, name: 'A' }, { _id: 2, name: 'Existing' } ]
unordered: MongoBulkWriteError 11000 inserted 2 failed at index [ 1 ]
[
{ _id: 1, name: 'A' },
{ _id: 2, name: 'Existing' },
{ _id: 3, name: 'C' }
]

ordered insert 1 document (A) แล้วหยุด: C ไม่ได้เข้าไป unordered insert 2 (A และ C) และรายงานความล้มเหลวเดียวที่ index 1 ทั้งสองแบบ error คือ MongoBulkWriteError (code 11000) และจำนวนที่เข้าไปได้อยู่ใน err.result.insertedCount (ใน Node มี err.insertedCount ด้วย) ทั้งสองโหมดไม่ rollback สิ่งที่ insert ไปแล้ว — ไม่มี all-or-nothing ที่นี่หากไม่ใช้ transaction

  • คุณใส่ _id เองได้ (string, ตัวเลข หรือค่าใดก็ได้) แทนที่จะให้ MongoDB สร้าง ObjectId — แต่ต้องไม่ซ้ำ ไม่เช่นนั้นจะได้ error E11000 ด้านบน
  • insertMany เป็น ordered โดยค่าเริ่มต้น: หยุดที่ document แรกที่ล้มเหลว ดังนั้น document หลัง error id ซ้ำจะไม่ถูก insert ส่ง { ordered: false } เพื่อไปต่อผ่านความล้มเหลวและ insert ทุก document ที่ไม่ชน
  • เพราะไม่มี schema สอง document ใน collection เดียวกันมี field ต่างกันสิ้นเชิงได้ การ insert ใจกว้าง นั่นคือฟีเจอร์ ไม่ใช่ bug — แค่ต้องคงความสม่ำเสมอโดยตั้งใจ

ผู้ช่วย AI ที่ถูกขอให้ “import สมาชิกอย่างปลอดภัย” มักอธิบายว่า insertMany เป็น atomic:

🐛 หา bug ในโค้ดนี้

// AI comment: "insertMany is all-or-nothing - if any member fails, none are saved"
async function importMembers(db, members) {
try {
await db.collection("members").insertMany(members);
return { ok: true };
} catch (err) {
return { ok: false, reason: err.message }; // safe: nothing was written
}
}
เฉลย

insertMany ไม่ใช่ transaction ในการรันจริงด้านบน เมื่อ document กลางชน document แรกถูก commit ไปแล้ว (inserted 1; A ยังอยู่ใน collection) ตอนที่ error ถูกส่งกลับ ข้อสมมติ “ไม่มีอะไรถูกเขียน” จึงทิ้ง batch ที่ import ครึ่งเดียวไว้ และผู้เรียกที่เชื่อว่า import ล้มเหลวอย่างสะอาด วิธีแก้คือตรวจผลบางส่วน — หรือเมื่อต้องการ all-or-nothing จริง ๆ ให้ห่อ insert ใน transaction (โมดูล 7) fence นี้รายงานสิ่งที่เข้าไปจริง:

app/insert-partial-result.mjs
const c = db.collection("members");
await c.deleteMany({});
await c.insertOne({ _id: 2, name: "Existing" });
let landed = null;
try {
await c.insertMany([{ _id: 1, name: "A" }, { _id: 2, name: "B" }, { _id: 3, name: "C" }]);
} catch (err) {
landed = err.result.insertedCount; // honest report: how many made it in
}
console.log({ ok: false, landed });
if (landed !== 1) throw new Error("expected exactly the first document to have landed");
{ ok: false, landed: 1 }

insert คือ คำขอสร้าง primary key ใหม่ ที่เหลือคือบัญชีรอบ ๆ: driver สร้าง ObjectId ให้ถ้าคุณไม่ใส่ server บังคับความไม่ซ้ำของ _id และ acknowledgement สะท้อน id กลับมา batch คือ ลำดับของ insert อิสระที่ส่งไปด้วยกัน — ordered ตัดสินว่าลำดับจะหยุดที่ความล้มเหลวแรกหรือไม่ ไม่ใช่ว่าทั้ง batch เป็น atomic

ตัวเลือกBenefitCost
insertOneเรียบง่าย จัดการ error ต่อ document ตรงไปตรงมาหนึ่ง round trip ต่อ document — ช้าเมื่อปริมาณมาก
insertMany (ordered)batch เดียว round trip น้อยลง จุดหยุดกำหนดได้แน่นอนdocument แรกที่ error หยุด batch; document ก่อนหน้าคงอยู่ ส่วนหลังไม่เคยถูก insert (inserted 1)
insertMany (unordered)ไปต่อผ่าน document ที่ล้มเหลว insert ทุกอันที่ไม่ชน (inserted 2)ลำดับการ insert ไม่รับประกัน; ต้องตรวจ writeErrors เพื่อรู้ว่าอันไหนล้มเหลว
insertMany ใน transactionall-or-nothingต้องมี replica set; overhead เพิ่มและอายุเริ่มต้น 60 วินาที
  • “insertMany เป็น atomic” — ไม่ใช่; การรัน ordered ด้านบนทิ้ง document แรกไว้ ใช้ transaction เมื่อต้องการ atomicity
  • “insert ทีละอันใน loop ก็ไม่เป็นไร” — เปลืองรอบเดินทาง; รวมเป็นชุดด้วย insertMany
  • “insertOne คืน document เต็มที่ insert” — คืน acknowledged และ insertedId ใช้ findOne ด้วย _id นั้นเพื่ออ่าน document
  • “ไม่มี error แปลว่าทุก document ถูก insert” — บน insertMany แบบ unordered จะโยน MongoBulkWriteError แต่โค้ดที่กลืน error จะพลาดความล้มเหลว; ตรวจ err.result.insertedCount และ err.writeErrors
  • “shell กับ driver คืนรูปร่างเดียวกัน” — ผล insertMany ใน mongosh ไม่มี insertedCount แต่ Node มี; ObjectId พิมพ์เป็น ObjectId('..'), new ObjectId('..'), ObjectID("..") ตามภาษา

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

reference ของ insertMany ระบุกฎที่เราวัด: “Excluding write concern errors, ordered operations stop after an error, while unordered operations continue to process any remaining write operations in the queue” และ insertMany() ใช้ภายใน distributed transaction ได้ (insertMany, เรียกอ่านเมื่อ 2026-09-30)

แท็บ Python/Go/Rust สะท้อนพฤติกรรมที่พิสูจน์แล้วของ shell/Node; แต่ละแท็บถูกรันหนึ่งครั้งในโปรแกรม scratch กับ server เดียวกัน (output ด้านบน) แต่ไม่ถูกรันโดยตัวตรวจอัตโนมัติของคอร์ส

ถ้า insert document โดยไม่มี field _id จะเกิดอะไรขึ้น?
โดยค่าเริ่มต้น insertMany แบบ ordered ทำอะไรเมื่อ document ที่สองจากสามมี _id ซ้ำ?
acknowledgement ของ insertMany มีอะไรบ้าง?
ด้วย { ordered: false } และ batch เดียวกัน (document กลางซ้ำ) insert ไปกี่ document และจำนวนอยู่ที่ไหน?
code ใดระบุ duplicate key ตอน insert?