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

single และ compound indexes

index ที่ง่ายที่สุดครอบคลุมฟิลด์เดียว เมื่อใดที่ query ของคุณกรองหรือเรียงบนสองฟิลด์พร้อมกัน คุณก็จะเอื้อมไปหา compound index — index อันเดียวที่เรียง document ตามหลายฟิลด์พร้อมกัน compound index คือที่ที่พลังจริงส่วนใหญ่อยู่ และเป็นที่ที่ความสับสนส่วนใหญ่อยู่ด้วย เพราะ compound index ไม่ได้ช่วยทุก query ที่เอ่ยถึงฟิลด์ในนั้น กฎที่ตัดสินว่า index ตัวหนึ่งรับใช้ query แบบไหนได้คือ prefix rule และนี่คือแนวคิดสำคัญที่สุดของบทเรียนนี้

ตลอดทั้งบทเรานี้เรายังคงใช้ collection members ของห้องสมุด แต่ละ document ถือ name, ปี joined, city และจำนวน fines

คุณได้พบ createIndex ในบทเรียนก่อนแล้ว ทิศทาง — 1 สำหรับจากน้อยไปมาก, -1 สำหรับจากมากไปน้อย — ควบคุมลำดับการเรียงที่เก็บไว้ใน index สำหรับ single-field index ทิศทางแทบไม่สำคัญสำหรับการกรอง เพราะ MongoDB เดิน index ได้ทั้งสองทาง แต่สำคัญมากตอน sort ตรงนี้เราทำ index fines จากน้อยไปมาก:

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

server ยืนยัน index ด้วยชื่อ:

{ "createdIndexes": ["fines_1"], "ok": 1 }

compound index ระบุมากกว่าหนึ่งฟิลด์ แต่ละฟิลด์มีทิศทางของตัวเอง ลำดับของฟิลด์ใน index ไม่ใช่เรื่องตกแต่ง แต่เป็นตัวกำหนดลำดับการเรียง โดยเรียงตามฟิลด์แรกก่อน แล้วตามฟิลด์ที่สองภายในแต่ละกลุ่มที่ค่าฟิลด์แรกเท่ากัน เป็นต้น ตรงนี้เราทำ index city จากน้อยไปมาก แล้วตามด้วย joined จากมากไปน้อย เพื่อให้สมาชิกจัดกลุ่มตาม city และภายในแต่ละ city เรียงให้ใหม่สุดมาก่อน:

db.members.createIndex({ city: 1, joined: -1 })

ชื่อที่ถูกสร้างขึ้นร้อยฟิลด์และทิศทางเข้าด้วยกัน:

{ "createdIndexes": ["city_1_joined_-1"], "ok": 1 }

compound index ให้บริการ query ได้ก็ต่อเมื่อ query นั้นใช้ left-prefix ของฟิลด์ใน index — ฟิลด์แรก, หรือสองฟิลด์แรก, หรือสามฟิลด์แรก เป็นต้น เริ่มจากซ้ายเสมอและไม่กระโดดข้าม index city_1_joined_-1 ของเรามี prefix ที่ใช้ได้สองชุด: city อย่างเดียว และ city รวมกับ joined

ดังนั้น index นี้ช่วย query ที่กรองบน city อย่างเดียว:

db.members.find({ city: "Berlin" })

และช่วย query ที่ใช้ city กับ joined พร้อมกัน แต่ไม่ช่วย query ที่กรองด้วย joined อย่างเดียว เพราะ joined ไม่ใช่ left-prefix — index ถูกเรียงตาม city ก่อน ปีที่เข้าร่วมจึงกระจายอยู่ทั่วโครงสร้าง ไม่มีทาง seek ตรงเข้าไปหาได้ query บน joined อย่างเดียวจะต้องมี index ของตัวเอง หรือ compound index ที่เริ่มต้นด้วย joined

คุณดูใน explain ได้ว่า query ตรงกับ prefix ใด ที่เป็นหัวข้อของบทเรียนถัดไป สำหรับตอนนี้ สิ่งที่ได้คือเรื่องโครงสร้าง: อ่าน compound index จากซ้ายไปขวา แล้ว query จะไต่ไปกับ index ได้ไกลเท่าที่ยังแตะฟิลด์นำหน้าติดต่อกันอยู่เท่านั้น

ใน Compass: แท็บ Indexes ลิสต์แต่ละ index พร้อมฟิลด์และทิศทางตามลำดับ คุณจึงอ่าน prefix ที่ใช้ได้ออกในพริบตา และยังบอกขนาดบนดิสก์ของแต่ละ index ให้ตัดสินได้ว่าคุ้มกับพื้นที่ที่กินไปหรือเปล่า

  • compound index ที่เลือกมาดีอันเดียวแทน single-field index หลายอันได้ index บน { city: 1, joined: -1 } ครอบ query บน city อย่างเดียวอยู่แล้ว ดังนั้น index { city: 1 } แยกต่างหากจะซ้ำซ้อน
  • ลำดับฟิลด์ใน compound index เป็นการตัดสินใจเชิงออกแบบ ไม่ใช่รายละเอียด สลับลำดับเมื่อไร ชุด query ที่ index รับใช้ได้ก็เปลี่ยนทันที
  • ทิศทางสำคัญสำหรับการเรียง ไม่ใช่สำหรับการจับคู่แบบ equality ธรรมดา index อ่านได้ทั้งไปข้างหน้าและถอยหลัง ดังนั้น { joined: 1 } และ { joined: -1 } ต่างก็ให้บริการตัวกรอง equality ธรรมดาบน joined ได้ดีพอกัน ความต่างจะปรากฏเมื่อคุณเรียง
  • การข้ามฟิลด์นำหน้าทำให้ prefix แตก query บนฟิลด์ที่สองอย่างเดียวใช้ index ไม่ได้ ไม่ว่าฟิลด์นั้นจะเลือกได้เฉพาะเจาะจงแค่ไหน
ตัวเลือกBenefitCost
single-field indexเรียบง่าย ครอบ query shape เดียวได้พอดี ดูแลง่ายครอบได้แค่ query ที่กรอง/เรียงบนฟิลด์นั้นฟิลด์เดียว
compound indexครอบได้หลาย query shape พร้อมกันถ้าลำดับฟิลด์ตรงกับ left-prefixใหญ่กว่า เขียนช้ากว่า และออกแบบผิดลำดับแล้วช่วยอะไรไม่ได้เลย
  • คิดว่า compound index ช่วย query ที่กรองบนฟิลด์ใดก็ได้ในนั้น — compound index ให้บริการเฉพาะ left-prefix เท่านั้น query ที่กรองเฉพาะฟิลด์ที่สองโดยข้ามฟิลด์แรกไปจะไม่ seek เลย
  • สร้าง single-field index ซ้ำซ้อนกับ prefix ของ compound index ที่มีอยู่แล้ว — ถ้ามี { city: 1, joined: -1 } อยู่แล้ว index { city: 1 } แยกต่างหากซ้ำซ้อนโดยสิ้นเชิง เสียพื้นที่และ write cost ฟรีๆ
  • สลับลำดับฟิลด์ใน compound index โดยไม่เช็คผลกับ query อื่นที่ใช้ index เดิม — ลำดับฟิลด์เป็นการตัดสินใจเชิงออกแบบ เปลี่ยนแล้ว query shape ที่เคย seek ได้อาจกลายเป็น COLLSCAN ทันที

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

ระบบ booking อย่าง Airbnb-style — ใช้ compound index บน { city: 1, checkIn: 1, price: 1 } เพื่อรองรับทั้ง query กรองตามเมือง เรียงตามวันที่เข้าพัก และกรองช่วงราคา ด้วย index อันเดียว แทนที่จะสร้าง single-field index แยกสามอัน

ทีม analytics ที่ query หลาย shape บน collection เดียวกัน — เจอปัญหาว่า compound index ที่ออกแบบไว้สำหรับ dashboard หนึ่งใช้กับ query ของอีกทีมไม่ได้เพราะลำดับฟิลด์ไม่ตรง left-prefix จึงต้องเพิ่ม index อีกตัวแทนที่จะพึ่ง index เดิม

มี compound index บน { city: 1, joined: -1 } query แบบไหนที่ index นี้ seek ให้ได้?
prefix rule พูดถึงอะไรเกี่ยวกับ compound index?
ทำไมลำดับฟิลด์จึงสำคัญใน compound index?