Index giống mục lục cuối sách: thay vì lật từng trang (quét toàn bảng O(n)), DB nhảy thẳng tới đúng dòng (O(log n) với B-tree). Bảng 10 triệu dòng: quét toàn bộ ~10s, tra qua index ~1ms.
Vài loại đáng biết:
- Covering index: index chứa đủ mọi cột query cần → DB chỉ đọc index, khỏi đọc bảng (Index Only Scan). CREATE INDEX ON users(email, name) phục vụ SELECT name ... WHERE email = ?.
- Partial index: chỉ index một phần dòng — CREATE INDEX ON users(email) WHERE deleted_at IS NULL nhỏ và nhanh hơn nếu query luôn lọc dòng còn sống.
Khi KHÔNG nên index: bảng quá nhỏ (quét còn nhanh hơn), cột ít giá trị khác nhau (boolean, status 2-3 trạng thái), bảng ghi rất nhiều (mỗi ghi phải cập nhật mọi index).
Lưu ý hay gặp: bọc hàm quanh cột làm mất index — WHERE LOWER(email) = ? không dùng index trên email; phải tạo functional index CREATE INDEX ON users(LOWER(email)).
An index is like the index at the back of a book: instead of flipping every page (a full table scan, O(n)), the DB jumps straight to the right row (O(log n) with a B-tree). On a 10-million-row table: full scan ~10s, index lookup ~1ms.
A few types worth knowing:
- Covering index: the index holds every column the query needs → the DB reads only the index, not the table (Index Only Scan). CREATE INDEX ON users(email, name) serves SELECT name ... WHERE email = ?.
- Partial index: indexes only a subset of rows — CREATE INDEX ON users(email) WHERE deleted_at IS NULL is smaller and faster if queries always filter to live rows.
When NOT to index: tiny tables (a scan is faster), low-cardinality columns (boolean, a status with 2-3 values), write-heavy tables (every write must update all indexes).
Common pitfall: wrapping a function around a column kills the index — WHERE LOWER(email) = ? won't use the index on email; create a functional index CREATE INDEX ON users(LOWER(email)).