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

การสร้างและค้นหา Indexes

Object store ทุกตัวใน IndexedDB จะใช้ primary key (ได้แก่ keyPath หรือ id ที่เพิ่มขึ้นอัตโนมัติ) เป็นตัวระบุ record วิธีนี้มีประสิทธิภาพสำหรับการค้นหา ด้วย primary key แต่หากต้องการค้นหาด้วยฟิลด์อื่น เช่น ค้นหา contacts ทั้งหมดตาม name คุณจำเป็นต้องใช้ index

Index คือโครงสร้างเรียงลำดับรองที่แมปค่าของฟิลด์ที่เลือกไปยัง primary key ของ record นั้น เมื่อสร้างแล้ว คุณสามารถเรียก get() หรือ getAll() บน index ได้เหมือนกับที่เรียกบน store โดยตรง

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 });
};
พารามิเตอร์ความหมาย
nameชื่อ index ที่จะใช้เรียกคืนในภายหลังด้วย store.index(name)
keyPathpath ของ property บนแต่ละ record ที่จะ index (เช่น 'name', 'address.city')
uniqueหาก true store จะปฏิเสธ record ที่มีค่า indexed ซ้ำกับที่มีอยู่
multiEntryหาก true และ property ที่ index เป็น array แต่ละ element ของ array จะมี entry เป็นของตัวเองใน 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 match
const 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

// Enforce unique emails
store.createIndex('by_email', 'email', { unique: true });

เมื่อใช้ unique: true การเรียก add() หรือ put() ใดๆ ที่จะสร้างค่า indexed ซ้ำกันจะเกิด ConstraintError ใช้ตัวเลือกนี้กับฟิลด์อย่างเช่น email หรือ username ที่ไม่อนุญาตให้มีซ้ำ

// 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 ก็ตาม

Browser Storage
ตัวเลือกBenefitCost
สร้าง 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 จะเพิ่มขึ้นเรื่อยๆ ตามการใช้งาน

คุณต้องเรียก objectStore.createIndex() ที่ไหนเพื่อเพิ่ม index ได้สำเร็จ?
การตั้งค่า { unique: true } บน index ทำอะไร?
คุณเรียก nameIndex.getAll() โดยไม่ส่งอาร์กิวเมนต์ จะได้ผลลัพธ์อะไร?
ระเบียนมีฟิลด์ tags: ["js", "idb"] ตัวเลือก createIndex ใดที่ทำให้ค้นหา "js" และ "idb" แยกกันได้ผ่าน index.getAll("js")?