กลับไปหน้าบทความ
#MongoDB#Index#Database Performance#Query Optimization#Explain Plan

ทำความเข้าใจ Index ใน MongoDB เพื่อแก้ Query ช้าอย่างตรงจุด

Index ใน MongoDB เปรียบเหมือนทางลัดที่ช่วยให้ฐานข้อมูลค้นหาเอกสารได้เร็วขึ้น แต่การใช้งานให้มีประสิทธิภาพต้องเริ่มจากการดู query จริงและอ่าน execution plan ก่อนเสมอ บทความนี้อธิบายแนวคิดสำคัญ วิธีใช้ explain() และหลักการ

18 กรกฎาคม 2569อ่านประมาณ 3 นาที

แชร์บทความ

ทำความเข้าใจ Index ใน MongoDB เพื่อแก้ Query ช้าอย่างตรงจุด

ทำความเข้าใจ Index ใน MongoDB เพื่อแก้ Query ช้าอย่างตรงจุด

Index ใน MongoDB คือ “ทางลัด” ที่ช่วยให้ฐานข้อมูลค้นหาเอกสารได้เร็วขึ้น หากไม่มี index เวลา query ด้วยเงื่อนไขบางอย่าง MongoDB อาจต้องไล่อ่าน document ทีละตัว ซึ่งไม่ต่างจากการเปิดหนังสือทุกหน้าเพื่อหาคำเพียงคำเดียว ยิ่งข้อมูลมีจำนวนมาก ระดับแสนหรือหลักล้านแถว ความช้าก็ยิ่งเห็นชัด

ทำไมไม่มี Index แล้ว Query ถึงช้า

ลองนึกถึง collection ชื่อ users ที่มี field เช่น email, status และ createdAt

ถ้าเราสั่ง query แบบนี้

find({ email: "a@site.com" })

แต่ไม่มี index บน field email สิ่งที่ MongoDB มักต้องทำคือ collection scan หรือการสแกนทั้ง collection เพื่อหาว่า document ไหนตรงเงื่อนไข

เมื่อข้อมูลมีขนาดใหญ่ ผลกระทบไม่ได้มีแค่ความช้าในการตอบ query เท่านั้น แต่ยังดึงทรัพยากรของระบบไปพร้อมกัน เช่น

  • CPU ทำงานหนักขึ้น
  • RAM ถูกใช้งานมากขึ้น
  • disk I/O สูงขึ้น

ดังนั้น query ที่ดูเหมือนง่ายมาก อาจกลายเป็นคอขวดของระบบได้ ถ้าไม่มี index ที่เหมาะสมรองรับ

วิธีเช็กว่า Query ช้าเพราะอะไรด้วย explain()

วิธีที่ตรงที่สุดในการวิเคราะห์ query บน MongoDB คือใช้ explain() เช่น

find({ email: "a@site.com" }).explain("executionStats")

ผลลัพธ์จาก explain() จะช่วยบอกว่า MongoDB ประมวลผล query อย่างไร โดยจุดสำคัญที่ควรดูคือ

  • stage: "COLLSCAN" หมายถึงมีการสแกนทั้ง collection และไม่ได้ใช้ index
  • stage: "IXSCAN" หมายถึง query นี้ใช้ index แล้ว
  • totalDocsExamined บอกจำนวน document ที่ต้องตรวจจริงระหว่างการค้นหา

ตัวอย่างเช่น ถ้า query คืนผลลัพธ์เพียง 1 document แต่ระบบต้องตรวจไปถึง 800,000 documents นั่นเป็นสัญญาณชัดเจนว่าประสิทธิภาพกำลังมีปัญหา

พูดอีกแบบคือ อย่าดูแค่ว่า query “รันได้” หรือไม่ แต่ต้องดูด้วยว่ามัน “รันอย่างไร”

Single Field Index: จุดเริ่มต้นที่สำคัญที่สุด

index แบบพื้นฐานที่สุดคือ single field index หรือ index บน field เดียว เช่น email ซึ่งมักใช้ค้นหาบ่อยและควรมีค่าไม่ซ้ำ

createIndex({ email: 1 }, { unique: true })

index แบบนี้เหมาะกับกรณีใช้งาน เช่น

  • การ login ด้วย email
  • การ lookup ข้อมูลผู้ใช้
  • การค้นหา order จาก orderId
  • การค้นหาเอกสารจากค่าที่มีความเฉพาะตัวสูง

field ที่มักคุ้มค่ากับการทำ index ได้แก่

  • email
  • slug
  • transactionId

หลักคิดง่าย ๆ คือ ถ้า field นั้นถูกค้นหาบ่อยและมีค่าที่ช่วยคัดข้อมูลได้แคบมาก การทำ index มักให้ผลลัพธ์ที่ดี

Compound Index: เมื่อ Query มีทั้ง Filter และ Sort

ในงานจริง query มักไม่ได้มีแค่เงื่อนไขเดียว เช่น

find({ status: "paid", userId: 123 }).sort({ createdAt: -1 })

ในกรณีนี้ การใช้ single field index อาจไม่พอ เพราะ query มีทั้ง filter และ sort หากต้องการให้ MongoDB ใช้ index ได้อย่างมีประสิทธิภาพ ควรพิจารณา compound index เช่น

createIndex({ userId: 1, status: 1, createdAt: -1 })

ข้อดีคือ MongoDB อาจใช้ index ตัวเดียวเพื่อช่วยทั้งการกรองข้อมูลและการเรียงลำดับได้พร้อมกัน ซึ่งลดภาระการสแกนข้อมูลและลดต้นทุนในการ sort ภายหลัง

อย่างไรก็ตาม ลำดับของ field ใน compound index มีผลมาก ถ้าเรียงผิด index อาจช่วยได้ไม่เต็มที่ หรือบางครั้งแทบไม่ช่วยเลย

หลัก ESR: Equality, Sort, Range

หลักที่ใช้จำการออกแบบ compound index ได้ง่ายมากคือ ESR

  • Equality: field ที่ใช้เท่ากับค่าชัดเจนมาก่อน
  • Sort: ตามด้วย field ที่ใช้เรียงลำดับ
  • Range: ปิดท้ายด้วย field ที่ใช้เงื่อนไขช่วง

ตัวอย่างเช่น

  • Equality: userId, status
  • Sort: createdAt
  • Range: price: { $gt: 100 }

แนวคิดนี้ไม่ได้เป็นกฎตายตัวในทุกกรณี แต่ถือเป็นจุดเริ่มต้นที่ดีมากในการออกแบบ index ให้สอดคล้องกับลักษณะของ query จริง

เมื่อเริ่มไม่แน่ใจว่าจะวาง field ไหนก่อน การใช้ ESR เป็นกรอบคิด จะช่วยลดการลองผิดลองถูกได้มาก

Index ไม่ได้ฟรี: สร้างมากไปก็มีต้นทุน

ข้อผิดพลาดที่พบบ่อยคือสร้าง index ไว้เยอะเกินไปเพราะคิดว่า “มีไว้ก่อนน่าจะดี” แต่ในความเป็นจริง index มีต้นทุนเสมอ

ผลกระทบของการมี index มากเกินไป ได้แก่

  • ใช้พื้นที่จัดเก็บเพิ่มขึ้น
  • ทุกครั้งที่มี insert, update, delete ระบบต้องอัปเดต index ตาม
  • ความเร็วในการเขียนข้อมูลอาจลดลง

ดังนั้น index ที่ดีไม่ใช่ index ที่เยอะที่สุด แต่คือ index ที่ตอบโจทย์ query สำคัญของระบบจริง ๆ

แนวทางที่เหมาะสมคือ สร้างจากพฤติกรรมการใช้งานจริง ไม่ใช่สร้างเผื่อในสิ่งที่อาจจะใช้ในอนาคต

Field ที่มีความแตกต่างน้อย อาจไม่คุ้มกับ Single Index

บาง field มีค่าที่แตกต่างกันน้อยมาก เช่น isDeleted ที่มีแค่ true หรือ false การสร้าง index เดี่ยวบน field แบบนี้มักไม่ช่วยมาก เพราะมันคัดข้อมูลออกได้ไม่แคบพอ

แต่ไม่ได้แปลว่า field ประเภทนี้ไม่มีประโยชน์เลย หากนำไปใช้ร่วมกับ field อื่นใน compound index ก็อาจคุ้มค่าได้ เช่น

createIndex({ isDeleted: 1, tenantId: 1, createdAt: -1 })

แนวทางนี้เหมาะเป็นพิเศษกับระบบแบบ multi-tenant ที่ต้องกรองข้อมูลตาม tenant และยังต้องการเรียงลำดับข้อมูลล่าสุดด้วย

การใช้ Regex กับ MongoDB ต้องระวัง

อีกกรณีหนึ่งที่มักทำให้ query ช้าคือการใช้ regex

ตัวอย่างที่ยังพอมีโอกาสใช้ index ได้ในบางกรณีคือ

/^abc/

แต่ถ้าเป็นรูปแบบที่มี wildcard นำหน้า เช่น

/abc/

MongoDB มักไม่สามารถใช้ index ได้อย่างมีประสิทธิภาพ โดยเฉพาะเมื่อทำงานกับ collection ขนาดใหญ่

ถ้าระบบต้องค้นหาข้อความอย่างจริงจัง ไม่ควรฝืนใช้ regex กับข้อมูลจำนวนมาก แต่ควรพิจารณาแนวทางที่เหมาะกว่า เช่น

  • text index
  • search engine
  • Atlas Search

การเลือกเครื่องมือให้เหมาะกับประเภทของการค้นหา มักให้ผลลัพธ์ดีกว่าการพยายามบีบให้ regex ทำงานเกินขอบเขตที่มันเหมาะ

แนวทางใช้งานจริง: เริ่มจาก Query ที่ช้า ไม่ใช่เริ่มจากเดา

เวลาจะแก้ปัญหา performance ใน MongoDB อย่าเริ่มจากคำถามว่า “ควรมี index อะไรบ้าง” แต่ให้เริ่มจากคำถามว่า “query ไหนช้า”

จากนั้นให้ทำตามลำดับนี้

  1. เปิด explain()
  2. ดูว่าเป็น COLLSCAN หรือ IXSCAN
  3. ดูจำนวน documents ที่ระบบต้องตรวจ
  4. วิเคราะห์ว่า query นี้มีเงื่อนไขแบบ filter, sort หรือ range อย่างไร
  5. ออกแบบ index ให้ตรงกับรูปแบบการใช้งานจริง

วิธีคิดแบบนี้ช่วยให้การเพิ่ม index มีเหตุผล รองรับของจริง และลดความเสี่ยงในการสร้าง index ที่ไม่จำเป็น

สรุป

Index ที่ดีใน MongoDB ไม่ใช่ index ที่มีจำนวนมาก แต่คือ index ที่ช่วยตอบคำถามของแอปพลิเคชันได้เร็ว ใช้พื้นที่อย่างพอดี และไม่ทำให้การเขียนข้อมูลช้าลงเกินความจำเป็น

ถ้า query ช้า อย่าเพิ่งเดา อย่าเพิ่งรีบสร้าง index แบบสุ่ม ๆ แต่ให้เริ่มจากดู execution plan ผ่าน explain() ก่อนเสมอ แล้วค่อยปรับ index ให้สอดคล้องกับ filter, sort และ range ของ query นั้น

สุดท้าย การปรับ performance ที่แม่นที่สุด ไม่ได้มาจากความรู้สึก แต่มาจากข้อมูลที่วัดได้จริง