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

ประเภทของ index

index แบบจากน้อยไปมากธรรมดาครอบคลุมความต้องการส่วนใหญ่ แต่ MongoDB มาพร้อม index ประเภทเฉพาะทางหลายชนิดสำหรับกรณีที่ index ธรรมดาจัดการได้ไม่สง่างาม: ฟิลด์ที่ถือ array, การค้นหาแบบ full-text, document ที่ควรหมดอายุเอง, index ที่ครอบเพียงชิ้นหนึ่งของ collection และ index ที่บังคับความไม่ซ้ำ แต่ละชนิดเป็นการปรับเปลี่ยนเล็กน้อยจาก createIndex และแค่รู้ว่ามีของพวกนี้อยู่ ก็ช่วยให้คุณไม่ต้องไปแก้ขัดแบบเก้งก้าง เราจะไล่ไปทีละชนิด โดยยังคงใช้ collection members ของห้องสมุดของเรา

เมื่อคุณทำ index บนฟิลด์ที่ถือ array MongoDB จะทำให้เป็น multikey index อัตโนมัติ คือทำ index ให้ทุกสมาชิกของ array แยกกัน query หาสมาชิกตัวใดตัวหนึ่งจึง seek ตรงไปยัง document ที่ match ได้ คุณไม่ได้สั่งให้เป็น multikey แต่เกิดขึ้นเองเพราะค่าของฟิลด์เป็น array ทำ index บน array borrowed ของชื่อหนังสือ:

db.members.createIndex({ borrowed: 1 })

query อย่างการหาทุกคนที่ยืม "Compilers" ตอนนี้ seek แทนที่จะสแกน แม้ค่าจะอยู่ภายใน array

text index ตัดคำ (tokenize) ฟิลด์ string เพื่อให้คุณค้นหาคำด้วยตัวดำเนินการ $text แทนการ match string แบบเป๊ะ ลองทำ text index บนฟิลด์ notes ของสมาชิกแล้ว query ดู:

db.members.createIndex({ notes: "text" })
db.members.find({ $text: { $search: "overdue reminder" } })

การค้นหาจับคู่คำใดคำหนึ่งและจัดอันดับผลลัพธ์ตามความเกี่ยวข้อง collection หนึ่งมี text index ได้อันเดียวเท่านั้น แม้ index อันนั้นจะครอบหลายฟิลด์ string ก็ตาม

TTL (time to live) index ลบ document อัตโนมัติเมื่อฟิลด์วันที่เก่ากว่าจำนวนวินาทีที่กำหนด สร้างได้ด้วยการทำ index บนฟิลด์วันที่พร้อม option expireAfterSeconds ตรงนี้ ระเบียน session ของสมาชิกจะหมดอายุหนึ่งชั่วโมงหลังจาก timestamp createdAt:

db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })

งานเบื้องหลังกวาดและลบ document ที่หมดอายุออก การกวาดทำงานราวหนึ่งครั้งต่อนาที ดังนั้นการลบจึงฉับไวแต่ไม่ทันที

partial index ครอบเฉพาะ document ที่ตรงกับนิพจน์ตัวกรอง ซึ่งทำให้ index เล็กเมื่อคุณ query เพียงชิ้นหนึ่งของ collection ทำ index เฉพาะสมาชิกที่ค้าง fines จริง โดยแนบ partialFilterExpression:

db.members.createIndex(
{ fines: 1 },
{ partialFilterExpression: { fines: { $gt: 0 } } }
)

สมาชิกที่มี fines เป็นศูนย์ไม่เคยเข้าสู่ index index จึงเล็กกระชับและยังรับใช้ query “ใครค้างเงิน” ได้ โดยไม่ต้องทำ index ทุกคน

unique index ปฏิเสธทุก insert หรือ update ที่จะทำให้ค่าที่ทำ index ซ้ำกัน — เป็นวิธีที่คุณรับประกัน เช่น ว่าไม่มีสมาชิกสองคนใช้ cardNumber ร่วมกัน:

db.members.createIndex({ cardNumber: 1 }, { unique: true })

sparse index ก้าวไปอีกขั้นด้วยการข้าม document ที่ขาดฟิลด์ที่ทำ index ไปเลย แทนที่จะเก็บเป็นค่า null การใช้ sparse คู่กับ unique จึงยอมให้ document จำนวนมากไม่มี cardNumber ได้ แต่ยังห้ามค่าซ้ำในกลุ่มที่มีค่าอยู่:

db.members.createIndex({ cardNumber: 1 }, { unique: true, sparse: true })

ใน Compass: ไดอะล็อก Create Index ในแท็บ Indexes เปิดเผยสิ่งเหล่านี้เป็นตัวเลือก — คุณติ๊ก Unique ตั้งค่า TTL หรือให้ Partial Filter Expression ได้โดยไม่ต้องออกจาก GUI

  • multikey index มีข้อจำกัด: compound index รวมฟิลด์ array ได้มากที่สุดหนึ่งฟิลด์ เพราะการทำ index สอง array พร้อมกันจะคูณกันออกมาเป็นรายการ index ที่ระเบิดมหาศาล
  • การลบของ TTL จับเวลาแบบโดยประมาณ การกวาดเบื้องหลังทำงานราวหนึ่งครั้งต่อนาที document ที่หมดอายุจึงอาจค้างอยู่เกินกำหนดได้ถึงราวหนึ่งนาทีก่อนโดนลบ
  • partial index ให้บริการ query ได้ก็ต่อเมื่อ query นั้นรับประกันว่าตกอยู่ภายในตัวกรองของ index query ที่อาจตรงกับ document ที่ถูกยกเว้นใช้ partial index ไม่ได้ แม้กับส่วนที่ทับซ้อนกันก็ตาม
  • unique index นับฟิลด์ที่หายไปว่าเป็นค่า null ถ้าไม่ใส่ sparse จึงมี document ที่ไม่มีฟิลด์นั้นได้แค่อันเดียว เพิ่ม sparse เมื่อ document จำนวนมากจะขาดฟิลด์นั้น
ตัวเลือกBenefitCost
text indexค้นหาคำแบบ full-text สะดวก ไม่ต้อง regexหนึ่ง collection มี text index ได้แค่อันเดียว
TTL indexลบ document ที่หมดอายุอัตโนมัติ ไม่ต้องเขียน job แยกเวลาลบไม่แม่นยำ เพราะงานกวาดพื้นหลังทำงานราวหนึ่งครั้งต่อนาที
partial indexเล็กกว่า index เต็ม เพราะครอบเฉพาะ document ที่ตรงตัวกรองช่วยเฉพาะ query ที่การันตีว่าตกอยู่ในตัวกรองของ index เท่านั้น
unique indexรับประกันความไม่ซ้ำของข้อมูลในระดับ databaseinsert หรือ update ที่ทำให้ค่าซ้ำจะโดน reject ทันที ต้องเขียน error path รองรับเพิ่ม
  • สร้าง text index สองอันบน collection เดียว — MongoDB อนุญาตให้มี text index ได้แค่อันเดียวต่อ collection แม้จะครอบหลายฟิลด์ก็ตาม ต้องรวมฟิลด์ที่ต้องการค้นหาไว้ใน index เดียว
  • คาดหวังว่า TTL index ลบ document ตรงเวลาเป๊ะ — งานกวาดพื้นหลังทำงานราวหนึ่งครั้งต่อนาที document อาจค้างเกินกำหนดได้นานถึงหนึ่งนาที อย่าพึ่ง TTL สำหรับ use case ที่ต้องการความแม่นยำระดับวินาที
  • ลืมใส่ sparse คู่กับ unique เมื่อฟิลด์เป็น optional — unique index นับค่าที่หายไปเป็น null เหมือนกันหมด ดังนั้นถ้าไม่ใส่ sparse จะมี document ได้แค่หนึ่งฉบับเท่านั้นที่ขาดฟิลด์นั้นได้

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

แพลตฟอร์ม SaaS ที่เก็บ session/token ชั่วคราว — ใช้ TTL index บนฟิลด์ expiresAt เพื่อให้ session หมดอายุลบตัวเองโดยไม่ต้องมี cron job แยกไว้ล้างข้อมูลเก่า

ระบบ support ticket ที่ทำ full-text search บนเนื้อหา ticket — ใช้ text index บนฟิลด์ subject และ body รวมกัน เพื่อให้ agent ค้นหา ticket ด้วยคำสำคัญได้โดยไม่ต้องพึ่ง search engine แยกต่างหากอย่าง Elasticsearch สำหรับข้อมูลปริมาณไม่ใหญ่มาก

เมื่อใดที่ MongoDB ทำให้ index เป็น multikey?
TTL index ทำอะไร?
ทำไมจึงรวม unique index กับ sparse?