Skip to content

Index: Vũ khí tối thượng

Nếu database là một cuốn sách dày 1000 trang, thì Index (Chỉ mục) chính là phần Mục lục ở cuối sách. Không có mục lục, bạn phải lật từng trang (Full Table Scan) để tìm tên "Nguyen Van A". Có mục lục, bạn biết ngay "Nguyen Van A" ở trang 582, 583.

Concept: Cấu trúc Index

1. B-Tree (Balanced Tree) - "The King"

99% trường hợp bạn sẽ dùng cái này.

  • Cấu trúc: Cây cân bằng, dữ liệu được sắp xếp.
  • Dùng cho: =, >, <, >=, <=, BETWEEN, IN, LIKE 'abc%'.
  • Độ phức tạp: O(logN). Tìm trong 1 triệu dòng chỉ mất khoảng 2-3 bước nhảy.

2. Hash Index

  • Cấu trúc: Bảng băm (Hash Table).
  • Dùng cho: Chỉ so sánh bằng =. Cực nhanh (O(1)).
  • Hạn chế: Không dùng được cho so sánh khoảng (>, <) hoặc sắp xếp (ORDER BY). Ít phổ biến hơn B-Tree.

Composite Index & The "Leftmost Prefix" Rule

Đây là kiến thức phân biệt Senior và Fresher. Giả sử bạn có Index đa cột: idx_name_age (name, age).

B-Tree sẽ sắp xếp theo name trước, sau đó nếu name trùng nhau thì xếp theo age.

Quy tắc "Trái nhất" (Leftmost Prefix): Index (A, B, C) chỉ có tác dụng nếu bạn query theo:

  1. A
  2. AB
  3. ABC

CÁC TRƯỜNG HỢP INDEX KHÔNG HOẠT ĐỘNG (DEAD):

  1. Chỉ query cột age (Bỏ qua cột đầu name):

    sql
    SELECT * FROM users WHERE age = 25; 
    -- Index idx_name_age VÔ DỤNG! Database phải Scan toàn bộ.

    Lý do: Giống như tìm những người "25 tuổi" trong danh bạ điện thoại (sắp xếp theo Tên). Bạn không thể tìm được nếu không duyệt từ đầu đến cuối.

  2. Bị đứt đoạn: Query WHERE A = 1 AND C = 3. Index chỉ dùng được cho A, phần C phải lọc thủ công.

Khi nào Index KHÔNG hoạt động?

Ngoài quy tắc Leftmost, Index còn bị "vô hiệu hóa" nếu:

  1. Dùng hàm lên cột Index:

    sql
    -- SAI: Index chết
    SELECT * FROM users WHERE YEAR(created_at) = 2024;
    
    -- ĐÚNG:
    SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
  2. Dùng toán tử phủ định: <>, !=, NOT IN thường không dùng Index hiệu quả.

  3. LIKE '%keyword': Bắt đầu bằng Wildcard là chết (như đã nói ở bài Filtering).

  4. Data Distribution: Nếu bạn lọc WHERE gender = 'Male' mà 50% dữ liệu là Male, Database sẽ chọn Full Scan cho nhanh thay vì nhảy qua lại Index.

🧠 Quiz

Câu 1: Composite Index (a, b, c) có thể tối ưu query nào?

  • [ ] A) WHERE b = 1 AND c = 2 (bỏ qua cột a)
  • [x] B) WHERE a = 1 AND b = 2 (tuân thủ leftmost prefix)
  • [ ] C) WHERE c = 1 (chỉ cột cuối)
  • [ ] D) Tất cả query trên đều được tối ưu

💡 Giải thích: B-tree composite index tuân thủ "leftmost prefix rule". Index (a,b,c) hỗ trợ: WHERE a, WHERE a AND b, WHERE a AND b AND c. KHÔNG hỗ trợ WHERE b hoặc WHERE c — phải bắt đầu từ cột trái nhất.

Câu 2: Khi nào Index KHÔNG hiệu quả?

  • [ ] A) Khi bảng có nhiều dòng
  • [ ] B) Khi dùng với PRIMARY KEY
  • [x] C) Khi dùng function trên cột (WHERE UPPER(name) = 'JOHN'), LIKE bắt đầu bằng %, hoặc cột có selectivity thấp
  • [ ] D) Khi dùng với JOIN

💡 Giải thích: Function wrap cột thì database không dùng index trên cột đó (dùng functional index nếu cần). LIKE '%abc' = full scan. Cột với ít giá trị distinct (gender: M/F) thì database chọn full scan nhanh hơn nhảy index.

Câu 3: Covering Index là gì và tại sao nhanh?

  • [ ] A) Index cover toàn bộ bảng
  • [ ] B) Index trên tất cả cột
  • [x] C) Index chứa đủ cột mà query cần — database đọc từ index mà không cần truy cập table (Index-Only Scan)
  • [ ] D) Index tự động tạo bởi database

💡 Giải thích: Query SELECT name FROM users WHERE email = 'x' — nếu index trên (email, name), database lấy cả email (để filter) và name (để return) từ index, không cần đọc table row. Giảm I/O đáng kể.