การสร้างและค้นหา Indexes
ทำไมต้องใช้ indexes?
หัวข้อที่มีชื่อว่า “ทำไมต้องใช้ indexes?”Object store ทุกตัวใน IndexedDB จะใช้ primary key (ได้แก่ keyPath หรือ id ที่เพิ่มขึ้นอัตโนมัติ) เป็นตัวระบุ record วิธีนี้มีประสิทธิภาพสำหรับการค้นหา ด้วย primary key แต่หากต้องการค้นหาด้วยฟิลด์อื่น เช่น ค้นหา contacts ทั้งหมดตาม name คุณจำเป็นต้องใช้ index
Index คือโครงสร้างเรียงลำดับรองที่แมปค่าของฟิลด์ที่เลือกไปยัง primary key ของ record นั้น เมื่อสร้างแล้ว คุณสามารถเรียก get() หรือ getAll() บน index ได้เหมือนกับที่เรียกบน store โดยตรง
การสร้าง index ด้วย createIndex()
หัวข้อที่มีชื่อว่า “การสร้าง index ด้วย createIndex()”Index ต้องถูกสร้าง (หรือลบ) ภายใน callback ของ onupgradeneeded เท่านั้น ไม่สามารถเพิ่ม index ให้กับ store ที่มีอยู่แล้วนอก upgrade transaction ได้
const request = indexedDB.open('mydb', 1);
request.onupgradeneeded = (event) => { const db = event.target.result;
// Create the object store const store = db.createObjectStore('contacts', { keyPath: 'id' });
// Create an index on the 'name' field store.createIndex('by_name', 'name', { unique: false });};พารามิเตอร์ของ createIndex(name, keyPath, options)
หัวข้อที่มีชื่อว่า “พารามิเตอร์ของ createIndex(name, keyPath, options)”| พารามิเตอร์ | ความหมาย |
|---|---|
name | ชื่อ index ที่จะใช้เรียกคืนในภายหลังด้วย store.index(name) |
keyPath | path ของ property บนแต่ละ record ที่จะ index (เช่น 'name', 'address.city') |
unique | หาก true store จะปฏิเสธ record ที่มีค่า indexed ซ้ำกับที่มีอยู่ |
multiEntry | หาก true และ property ที่ index เป็น array แต่ละ element ของ array จะมี entry เป็นของตัวเองใน index |
การเปิด index และการค้นหา
หัวข้อที่มีชื่อว่า “การเปิด index และการค้นหา”หลังจาก upgrade เสร็จสิ้น ให้เปิด index จาก transaction อ่าน/เขียนปกติด้วย store.index('by_name') จากนั้นเรียก get(value) สำหรับ record เดียว หรือ getAll(value) สำหรับ record ที่ตรงกันทั้งหมด
const tx = db.transaction('contacts', 'readonly');const store = tx.objectStore('contacts');const nameIndex = store.index('by_name');
// Single matchconst getReq = nameIndex.get('Alice');getReq.onsuccess = () => console.log(getReq.result); // { id: 1, name: 'Alice', ... }
// All records (no filter → returns every record sorted by index key)const getAllReq = nameIndex.getAll();getAllReq.onsuccess = () => console.log(getAllReq.result);การส่งค่าให้ getAll(value) จะกรองเฉพาะ record ที่ฟิลด์ indexed ตรงกับค่านั้น หากไม่ส่งอาร์กิวเมนต์ (หรือส่ง undefined) จะคืนค่าทุก record ใน store โดยเรียงตาม index key
แฟล็ก unique
หัวข้อที่มีชื่อว่า “แฟล็ก unique”// Enforce unique emailsstore.createIndex('by_email', 'email', { unique: true });เมื่อใช้ unique: true การเรียก add() หรือ put() ใดๆ ที่จะสร้างค่า indexed ซ้ำกันจะเกิด ConstraintError ใช้ตัวเลือกนี้กับฟิลด์อย่างเช่น email หรือ username ที่ไม่อนุญาตให้มีซ้ำ
แฟล็ก multiEntry
หัวข้อที่มีชื่อว่า “แฟล็ก multiEntry”// Record: { id: 1, tags: ['js', 'idb', 'storage'] }store.createIndex('by_tag', 'tags', { multiEntry: true });เมื่อใช้ multiEntry: true ฟิลด์ที่มีค่าเป็น array จะสร้าง index entry หนึ่งรายการต่อ element คุณสามารถเรียก index.getAll('idb') และ record ข้างต้นจะถูกคืนค่า แม้ว่าค่าของ tags จะเป็น array ก็ตาม
ตัวอย่างรันได้: สร้าง index และค้นหา
หัวข้อที่มีชื่อว่า “ตัวอย่างรันได้: สร้าง index และค้นหา”ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| สร้าง index บนฟิลด์ที่ค้นหาบ่อย | อ่าน/ค้นหาด้วยฟิลด์นั้นเร็วขึ้นมาก ไม่ต้องสแกนทุก record | ทุกครั้งที่เขียน record ต้องอัปเดต index เพิ่มด้วย ทำให้เขียนช้าลง และกิน storage เพิ่ม |
| ไม่สร้าง index เลย (ค้นหาผ่าน cursor + filter เอง) | ไม่มี overhead ตอนเขียน ไม่กิน storage เพิ่ม | อ่าน/ค้นหาต้องสแกนทุก record ช้าลงเมื่อ store มีข้อมูลมาก |
ใช้ multiEntry: true กับฟิลด์ array | ค้นหาแต่ละ element ใน array ได้อย่างมีประสิทธิภาพ | เพิ่มจำนวน index entry ต่อ record หนึ่งตัว ทำให้ index มีขนาดใหญ่ขึ้นตามจำนวน element |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- พยายามสร้าง index ใหม่นอก
onupgradeneeded— จะโยน error ทันที index สร้างหรือลบได้เฉพาะภายใน upgrade transaction เท่านั้น - ตั้ง
unique: trueกับฟิลด์ที่มีค่าซ้ำกันอยู่แล้ว — การเขียน record ที่มีค่า indexed ซ้ำจะเกิดConstraintErrorทันที ต้องมั่นใจว่าฟิลด์นั้นไม่ซ้ำกันจริงก่อนเปิดใช้ตัวเลือกนี้ - ลืมว่า
getAll()บน index จะเรียงตาม index key ไม่ใช่ primary key — ถ้าคาดหวังลำดับตาม primary key แต่ query ผ่าน index ผลลัพธ์ที่ได้จะเรียงตามฟิลด์ที่ index ไว้แทน
💡 ตัวอย่างจากของจริง
Figma — สร้าง index บนฟิลด์อย่าง
updatedAtหรือprojectIdใน local cache เพื่อให้ query ไฟล์ design ล่าสุดหรือไฟล์ในโปรเจกต์เดียวกันได้เร็วโดยไม่ต้องสแกน record ทั้งหมดSpotify Web Player — ใช้ index บนฟิลด์ที่เกี่ยวกับ playlist หรือ track ID เพื่อให้ค้นหาข้อมูลที่ cache ไว้ได้รวดเร็ว แม้ว่าจำนวน record จะเพิ่มขึ้นเรื่อยๆ ตามการใช้งาน