การ Query ใน MongoDB
การใส่ข้อมูลเข้าไปนั้นง่าย ทักษะที่แยกคนที่แค่ เก็บ document ออกจากคนที่ใช้ฐานข้อมูลได้จริงคือการอ่านข้อมูลกลับมาอย่างแม่นยำ การ query คือศิลปะของการอธิบายอย่างเจาะจงและไม่เกินจำเป็นว่าคุณสนใจ document ชุดย่อยกลุ่มไหน — แล้วจัดรูปสิ่งที่ส่งกลับมาให้คุณได้รับเฉพาะ field ที่ต้องการ ในลำดับที่ต้องการ แบ่งเป็นหน้าเล็กพอที่จะจัดการได้ โมดูลนี้พูดถึงครึ่งหลังของฐานข้อมูลทั้งหมด นั่นคือการอ่าน
เราจะทำงานกับ collection จำลองเล็ก ๆ ของสมาชิกห้องสมุด members และหนังสือที่พวกเขายืม เพราะข้อมูลขนาดเล็กช่วยให้ query เป็นจุดสนใจ เมื่อจบบทคุณจะสามารถ filter ด้วยการเปรียบเทียบและการรวม logic เจาะลึกเข้าไปใน array และ sub-document ที่ฝังอยู่ พร้อมทั้งตัด sort และแบ่งหน้าผลลัพธ์ได้โดยไม่ต้องลาก collection ทั้งก้อนข้ามสายไป
flowchart LR Q["Your query"] --> F["Filter: which documents"] Q --> P["Projection: which fields"] Q --> O["Order: sort"] Q --> Pg["Page: limit and skip"] F --> R["Result cursor"] P --> R O --> R Pg --> R R --> App["Your app"]
query คือคำอธิบาย ไม่ใช่ลูปค้นหา
หัวข้อที่มีชื่อว่า “query คือคำอธิบาย ไม่ใช่ลูปค้นหา”เมื่อคุณ query MongoDB คุณไม่เคยเขียนลูป คุณส่ง filter document ให้ server ที่ อธิบาย document ที่คุณต้องการ แล้ว server จะหาให้ filter อย่างด้านล่างอ่านได้ว่า “สมาชิกทุกคนที่เข้าร่วมในปี 2023 และตอนนี้ไม่ค้างค่าปรับเลย”
db.members.find({ joined: 2023, fines: 0 })filter ที่เรียบง่ายที่สุดจับคู่ชื่อ field กับค่าหนึ่งค่า และหลายคู่ใน filter เดียวกันคือ AND โดยปริยาย จากจุดนั้นการ query เติบโตได้สี่ทิศทาง และโมดูลนี้จะพาไปทีละทิศทาง
โมดูลนี้ครอบคลุมอะไรบ้าง
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไรบ้าง”- Comparison และ logical operator มากกว่าการจับคู่แบบตรงเป๊ะ: มากกว่า, น้อยกว่า, การเป็นสมาชิกในเซ็ต, การไม่มี field และวิธีรวมเงื่อนไขด้วย AND/OR/NOT แบบชัดเจน เหล่านี้คือเครื่องมือใช้งานประจำวัน
- การ query array field หนึ่งสามารถเก็บ list ได้ คุณจะได้เรียนการจับคู่ element เดียว การบังคับหลายเงื่อนไขบน element เดียวกัน ด้วย
$elemMatchการบังคับให้มีหลายค่าครบทุกค่าด้วย$allและการ filter ตามความยาวของ array - การ query nested document document ซ้อนกันได้ dot notation ช่วยให้คุณเข้าถึง field ที่ฝังลึกอยู่ใน object และมีความแตกต่างสำคัญระหว่างการจับคู่ทั้ง sub-document แบบตรงเป๊ะกับการ match แค่ field เดียวข้างใน
- Projection, การ sort และการแบ่งหน้า เมื่อคุณได้ document ที่ถูกต้องแล้ว คุณตัดสินใจว่าจะส่ง field ไหนกลับมา จะมาในลำดับใด และจะแบ่งผลลัพธ์ก้อนใหญ่ออกเป็นหน้าที่จัดการได้อย่างไร — และทำไมลูกเล่นการแบ่งหน้ายอดนิยมตัวหนึ่งถึงช้าลงเมื่อข้อมูลใหญ่ขึ้น
ทุกบทเรียนแสดง query เดียวกันห้าแบบ — ใน mongosh แล้วตามด้วย driver ของ Node.js, Python, Go และ Rust — คู่กับ JSON document ที่จะส่งกลับมา เพื่อให้คุณอ่านรูปร่างของคำตอบได้ ไม่ใช่แค่ตัวคำสั่ง
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Filter อย่างเดียว ไม่ระบุ projection | เขียนเร็ว ใช้ query ได้ทันที | ส่ง field ที่ไม่ได้ใช้กลับมาทุกครั้ง เปลืองแบนด์วิดท์และ memory ฝั่ง client |
| Filter + projection ที่ระบุ field ชัดเจน | ลดขนาดข้อมูลที่ส่งผ่านเครือข่าย บังคับให้ทีมเห็นรายการ field ที่แอปพึ่งพาจริง | ต้องดูแล field list ให้ตรงกับ schema ที่เปลี่ยนไปเรื่อย ๆ |
| filter แบบจับคู่ตรงเป๊ะ | เร็วและ index ได้เสมอ | ตอบคำถามเชิงช่วง/เซ็ต/นิพจน์ซับซ้อนไม่ได้ |
filter ที่ซับซ้อนด้วย operator หลายตัวและ $and/$or | ตอบคำถามได้ละเอียดขึ้นมาก | อ่านยากขึ้น และเสี่ยงบังคับให้ query planner เลือก index ที่ไม่เหมาะสม |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ใช้
$whereเพื่อเขียนเงื่อนไขที่ operator มาตรฐานทำไม่ได้ —$whereรัน JavaScript ต่อ document จึงช้ามาก ใช้ index ไม่ได้เลย และเปิดช่องให้เกิดปัญหาด้าน security หากรับ input จากผู้ใช้ ให้มองหา operator มาตรฐานหรือ aggregation ก่อนเสมอ - เขียน regex แบบไม่ยึด anchor เช่น
/foo/แทน/^foo/— regex ที่ไม่มี^บังคับให้ MongoDB สแกนทุก document ในคอลเลกชัน ใช้ index ไม่ได้แม้ field นั้นจะมี index อยู่ก็ตาม ถ้าต้องการค้นแบบ “ขึ้นต้นด้วย” ให้ anchor ที่จุดเริ่มต้นเสมอ - คิดว่าการไม่ระบุ projection ไม่มีต้นทุน — ทุก field ที่ไม่ได้ตัดออกจะวิ่งข้ามเครือข่ายมา แล้วฝั่งแอปต้อง serialize ทั้งหมด สำหรับ document ที่มี field ใหญ่ (description ยาว ๆ หรือ array ขนาดใหญ่) การลืม projection คือสาเหตุ latency ที่พบบ่อย
💡 ตัวอย่างจากของจริง
หน้า filter สินค้าของอีคอมเมิร์ซ — sidebar ที่กรองตามหมวดหมู่ ช่วงราคา แบรนด์ และ rating ผูกตรงกับ comparison/logical operator (
$gte,$lte,$in) และใช้ projection ส่งกลับเฉพาะ field ที่การ์ดสินค้าต้องใช้ (ชื่อ ราคา รูปหลัก) ไม่ใช่ document เต็มแผง search ฝั่ง admin — เครื่องมือค้นหาผู้ใช้หรือ order ภายในองค์กรมักเผลอเปิดช่องให้ผู้ใช้พิมพ์ regex เองแล้วส่งตรงเข้า query โดยไม่ anchor ทำให้หน้าค้นหาช้าลงเรื่อย ๆ เมื่อข้อมูลโตขึ้น