การ insert document
การ insert คือ operation ที่ผ่อนคลายที่สุดใน MongoDB คุณยื่น document ให้ฐานข้อมูล ฐานข้อมูลเก็บไว้ แล้วตอบกลับมาว่าเก็บอะไรไป ไม่ต้องนิยาม schema ล่วงหน้า ไม่มีคอลัมน์ของตารางให้จัดให้ตรงกัน — ขอแค่ document เป็นข้อมูลคล้าย JSON ที่ถูกต้องก็เข้าได้เลย
อิสระตรงนี้แหละคือเหตุผลว่าทำไมวินัยเล็ก ๆ รอบ field _id ถึงคุ้มค่า เพราะเป็นงานเก็บบันทึกชิ้นเดียวที่ทุกการ insert ต้องยุ่งด้วย
เราจะสร้างห้องสมุดสมมติกันต่อ สมาชิกแต่ละคนคือ document หนึ่งรายการ และบทเรียนนี้เราจะเพิ่มสมาชิกเข้าไปใน collection ชื่อ members
การ insert document เดียว
หัวข้อที่มีชื่อว่า “การ insert document เดียว”insertOne รับ document มาหนึ่งรายการพอดีแล้วเก็บลง collection ถ้าคุณไม่ได้ระบุ _id มาเอง MongoDB จะสร้างให้ในรูปของ ObjectId — ค่าขนาด 12 ไบต์ที่ไม่ซ้ำกันทั่วทั้ง collection และเข้ารหัสเวลาที่สร้างเอาไว้ในตัว ตัว id ที่ได้นี้แหละคือ primary key ของ document
db.members.insertOne({ name: "Grace", joined: 2024, fines: 0 })const res = await db.collection("members").insertOne({ name: "Grace", joined: 2024, fines: 0,});console.log(res.insertedId);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 ที่กำหนดให้กลับมาด้วย ในเชลล์จะหน้าตาแบบนี้:
{ "acknowledged": true, "insertedId": "65f0b3d4e4b0a1c2e4b0a1d0" }ส่วน document ที่นอนอยู่ใน collection ตอนนี้หน้าตาแบบนี้ — _id คือสิ่งเดียวที่คุณไม่ได้พิมพ์เอง:
{ "_id": "65f0b3d4e4b0a1c2e4b0a1d0", "name": "Grace", "joined": 2024, "fines": 0}ใน Compass: เปิด collection members คลิกปุ่มสีเขียว ADD DATA เลือก Insert Document วาง JSON ลงไปโดยไม่ต้องใส่ _id แล้ว Compass จะเติม id ให้เองตอนกดบันทึก
การ insert หลายรายการในครั้งเดียว
หัวข้อที่มีชื่อว่า “การ insert หลายรายการในครั้งเดียว”เมื่อมี document เป็นชุด insertMany ส่งทั้งชุดไปใน round trip เดียว แทนที่จะเรียกทีละครั้งต่อหนึ่ง document คุณส่ง array เข้าไป แล้วได้ id ของทุกรายการที่ insert สำเร็จกลับมา โดยใช้ตำแหน่งใน array เป็น key
db.members.insertMany([ { name: "Linus", joined: 2023, fines: 2 }, { name: "Margaret", joined: 2025, fines: 0 }, { name: "Edsger", joined: 2022, fines: 5 }])const 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.insertedCount);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 จับคู่ index ของแต่ละรายการใน array เข้ากับ id ที่สร้างให้:
{ "acknowledged": true, "insertedIds": { "0": "65f0b3d4e4b0a1c2e4b0a1d1", "1": "65f0b3d4e4b0a1c2e4b0a1d2", "2": "65f0b3d4e4b0a1c2e4b0a1d3" }}เคล็ดลับและจุดที่ต้องระวัง
หัวข้อที่มีชื่อว่า “เคล็ดลับและจุดที่ต้องระวัง”- คุณกำหนด
_idเองได้ (เป็น string, number หรือค่าอะไรก็ได้) แทนการปล่อยให้ MongoDB สร้าง ObjectId — แต่ค่าต้องไม่ซ้ำ ถ้า insert document ที่_idมีอยู่แล้ว จะล้มเหลวพร้อม error แบบ duplicate key insertManyเป็นแบบ ordered โดยปริยาย คือหยุดทันทีที่ document ตัวแรกล้มเหลว ตัวที่อยู่ถัดจาก error เรื่อง id ซ้ำจึงไม่ได้เข้า collection เลย ถ้าอยากให้ไปต่อแม้เจอตัวที่ล้มเหลว แล้ว insert ทุกตัวที่ไม่ชนกัน ให้ส่ง option แบบ unordered เข้าไป- ไม่มี schema มาบังคับ ทำให้ document สองรายการใน collection เดียวกันมี field ต่างกันคนละเรื่องได้ การ insert ใจดีแบบนี้ถือเป็นฟีเจอร์ ไม่ใช่บั๊ก — แต่คุณต้องตั้งใจรักษาความสม่ำเสมอเอง
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
insertOne | เรียบง่าย จัดการ error ของแต่ละ document ตรงไปตรงมา | หนึ่ง round trip ต่อหนึ่ง document ช้าเมื่อมีจำนวนมาก |
insertMany (ordered) | batch เดียว ลด round trip | document ตัวแรกที่ error หยุดทั้งชุด ตัวที่เหลือไม่ได้เข้า |
insertMany (unordered) | ไปต่อแม้เจอ document ที่ error แล้ว insert ทุกตัวที่ไม่ชนกัน | ลำดับการ insert ไม่การันตี ต้องแกะผลลัพธ์เพื่อรู้ว่าตัวไหนล้มเหลว |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- insert document ทีละตัวในลูปแทนที่จะใช้
insertMany— เสีย round trip โดยไม่จำเป็น ยิ่งจำนวน document เยอะยิ่งช้าเห็นได้ชัด ควรรวมเป็น array แล้วเรียกinsertManyครั้งเดียว - คิดว่า
insertOneคืน document ที่ insert ไปกลับมาเต็ม ๆ — จริง ๆ คืนแค่ acknowledgement กับinsertedIdเท่านั้น ถ้าอยากได้ document เต็ม ต้องfindOneด้วย_idนั้นอีกที - ไม่ตรวจ
insertedCountของinsertManyแบบ unordered — เข้าใจว่า “ไม่มี error โยนขึ้นมา” แปลว่า document ทุกตัวถูก insert ครบ ทั้งที่ unordered อาจข้าม document ที่ล้มเหลวไปเงียบ ๆ ต้องเทียบinsertedCountกับจำนวนที่ส่งเข้าไป
💡 ตัวอย่างจากของจริง
ระบบ import สินค้าเข้าคลัง (e-commerce) — เมื่อ merchant อัปโหลดไฟล์ CSV สินค้าหลายพันรายการ ระบบแปลงแต่ละแถวเป็น document แล้วส่งเข้า
insertManyเป็นชุด (batch) ละ 500 รายการ แทนที่จะinsertOneทีละแถวระบบ log/event ingestion — service ที่รับ event จำนวนมากต่อวินาที มักสะสม event ไว้ใน buffer สั้น ๆ แล้ว
insertManyแบบ unordered เป็นก้อนเดียว เพื่อไม่ให้ event หนึ่งตัวที่ผิดรูปแบบไปบล็อก event อื่นทั้งหมด