การ 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 และเวลาบนเครื่องของคุณจะต่างออกไป
การ insert document เดียว
หัวข้อที่มีชื่อว่า “การ insert document เดียว”insertOne รับ document เดียวพอดีแล้วเก็บไว้ ถ้าคุณไม่ใส่ _id MongoDB จะสร้างให้เป็น ObjectId — ค่าขนาด 12 ไบต์ที่ไม่ซ้ำกันทั้ง collection และเข้ารหัสเวลาที่สร้างไว้ด้วย id ที่สร้างนี้คือ primary key ของ document
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);const res = await db.collection("members").insertOne({ name: "Grace", joined: 2024, fines: 0,});console.log(res);if (!res.acknowledged) throw new Error("not acknowledged");res = db.members.insert_one({"name": "Grace", "joined": 2024, "fines": 0})print(res.inserted_id)res, err := coll.InsertOne(ctx, bson.M{"name": "Grace", "joined": 2024, "fines": 0})if err != nil { return err}fmt.Println(res.InsertedID)let res = coll .insert_one(doc! { "name": "Grace", "joined": 2024, "fines": 0 }) .await?;println!("{}", res.inserted_id);acknowledgement บอกว่า operation สำเร็จและส่ง id ที่ถูกกำหนดกลับมา สิ่งที่แต่ละแท็บพิมพ์จริง (ObjectId ถูกพิมพ์ต่างกันในแต่ละ driver — เป็น 12 ไบต์เดียวกัน):
mongosh { acknowledged: true, insertedId: ObjectId('6abca7761ca8500949f70bb3') }Node { acknowledged: true, insertedId: new ObjectId('6abca7762e488e625dd180ed') }Python 6abca733ec5eb1df4567945dGo 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 ให้
insert หลายรายการพร้อมกัน
หัวข้อที่มีชื่อว่า “insert หลายรายการพร้อมกัน”เมื่อมี document เป็นชุด insertMany ส่งทั้งหมดในการเดินทางครั้งเดียวแทนที่จะเรียกทีละ document คุณส่ง array เข้าไป และได้ id ของทุกอย่างที่ถูก insert กลับมา โดยอ้างด้วยตำแหน่งใน array
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 insertedCountconst res = await db.collection("members").insertMany([ { name: "Linus", joined: 2023, fines: 2 }, { name: "Margaret", joined: 2025, fines: 0 }, { name: "Edsger", joined: 2022, fines: 5 },]);console.log(res);if (res.insertedCount !== 3) throw new Error("expected 3");res = db.members.insert_many([ {"name": "Linus", "joined": 2023, "fines": 2}, {"name": "Margaret", "joined": 2025, "fines": 0}, {"name": "Edsger", "joined": 2022, "fines": 5},])print(len(res.inserted_ids))docs := []interface{}{ bson.M{"name": "Linus", "joined": 2023, "fines": 2}, bson.M{"name": "Margaret", "joined": 2025, "fines": 0}, bson.M{"name": "Edsger", "joined": 2022, "fines": 5},}res, err := coll.InsertMany(ctx, docs)if err != nil { return err}fmt.Println(len(res.InsertedIDs))let docs = vec![ doc! { "name": "Linus", "joined": 2023, "fines": 2 }, doc! { "name": "Margaret", "joined": 2025, "fines": 0 }, doc! { "name": "Edsger", "joined": 2022, "fines": 5 },];let res = coll.insert_many(docs).await?;println!("{}", res.inserted_ids.len());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)เมื่อ insert ล้มเหลว: _id ซ้ำ
หัวข้อที่มีชื่อว่า “เมื่อ insert ล้มเหลว: _id ซ้ำ”_id ต้องไม่ซ้ำ insert document ที่สองด้วย id ที่มีอยู่แล้วจะได้ error duplicate key code 11000 พร้อมค่าที่ชนกันใน message:
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 untouchedMongoServerError 11000E11000 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 เทียบ unordered
หัวข้อที่มีชื่อว่า “insertMany แบบ ordered เทียบ unordered”insertMany เป็น ordered โดยค่าเริ่มต้น: หยุดที่ document แรกที่ล้มเหลว ด้วย { ordered: false } มันพยายามทุก document แล้วรายงานความล้มเหลวตอนท้าย เราพิสูจน์ทั้งสองแบบด้วย batch เดียวกัน — _id: 2 มีอยู่แล้ว document กลางจากสามจึงชน:
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 — แต่ต้องไม่ซ้ำ ไม่เช่นนั้นจะได้ errorE11000ด้านบน insertManyเป็น ordered โดยค่าเริ่มต้น: หยุดที่ document แรกที่ล้มเหลว ดังนั้น document หลัง error id ซ้ำจะไม่ถูก insert ส่ง{ ordered: false }เพื่อไปต่อผ่านความล้มเหลวและ insert ทุก document ที่ไม่ชน- เพราะไม่มี schema สอง document ใน collection เดียวกันมี field ต่างกันสิ้นเชิงได้ การ insert ใจกว้าง นั่นคือฟีเจอร์ ไม่ใช่ bug — แค่ต้องคงความสม่ำเสมอโดยตั้งใจ
อ่านโค้ดที่ AI สร้าง
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI สร้าง”ผู้ช่วย 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 นี้รายงานสิ่งที่เข้าไปจริง:
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 }Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”insert คือ คำขอสร้าง primary key ใหม่ ที่เหลือคือบัญชีรอบ ๆ: driver สร้าง ObjectId ให้ถ้าคุณไม่ใส่ server บังคับความไม่ซ้ำของ _id และ acknowledgement สะท้อน id กลับมา batch คือ ลำดับของ insert อิสระที่ส่งไปด้วยกัน — ordered ตัดสินว่าลำดับจะหยุดที่ความล้มเหลวแรกหรือไม่ ไม่ใช่ว่าทั้ง batch เป็น atomic
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
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 ใน transaction | all-or-nothing | ต้องมี replica set; overhead เพิ่มและอายุเริ่มต้น 60 วินาที |
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “
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 ด้านบน) แต่ไม่ถูกรันโดยตัวตรวจอัตโนมัติของคอร์ส