
Trả lời nhanh: Index là mục lục của bảng dữ liệu: thay vì lật từng trang (quét từng dòng) để tìm số điện thoại khách, database tra mục lục và nhảy thẳng đến chỗ cần, khác biệt giữa quét 2 triệu dòng và đọc vài chục node cây B-tree, tức nhanh hơn hàng trăm lần trên bảng lớn. Giá phải trả: mỗi lần ghi (INSERT/UPDATE/DELETE) phải cập nhật cả mục lục, ghi chậm đi một phần, tốn thêm dung lượng. Quy tắc thực dụng: index cột xuất hiện trong WHERE, JOIN, ORDER BY; đừng index cột hiếm dùng để lọc.
Không kiến thức database nào có tỷ lệ lợi-ích-trên-công-học cao hơn index: hiểu một khái niệm, gõ một dòng lệnh, query từ 4 giây về 40 mili-giây. Bài này giải thích index từ trực giác đến thực hành, đủ để bạn (hoặc agent AI của bạn) tự đặt index đúng chỗ cho dự án của mình.
- Index = cây B-tree sắp thứ tự trỏ vào dòng dữ liệu, tra vài chục bước thay vì quét triệu dòng
- Đọc nhanh gấp trăm lần; ghi chậm đi ~10-30% mỗi index, đặt có chọn lọc
- Index kép (a,b): lọc theo a hoặc a+b thì ăn; lọc mỗi b thì không, thứ tự cột là chiến lược
- Index giảm cả I/O: ít dòng đọc = ít lượt xuống ổ = máy đông khách vẫn thở đều
Trực giác: cuốn danh bạ và cuốn nhật ký
Bảng không index như cuốn nhật ký: dữ liệu nằm theo thứ tự nhập vào, muốn tìm ai phải đọc từ đầu. Index biến nó thành danh bạ: bản sao thu gọn của một cột, sắp thứ tự sẵn, kèm con trỏ về dòng gốc. Cấu trúc thật là cây B-tree: bảng 2 triệu dòng chỉ cần ~20 bước so sánh là đến đích, và mỗi bước là một node nhỏ, thường đã nằm sẵn trong RAM cache. Đó là phép màu "trăm lần" không có gì thần bí.
Cái giá: thuế ghi và dung lượng
Mỗi INSERT giờ phải chèn vào đúng vị trí của mọi index; UPDATE cột có index phải sửa cây; cây thi thoảng phải tách node, cân bằng lại. Bảng 5 index có thể ghi chậm hơn bảng trần 20-30%, chưa kể dung lượng index có khi ngang dữ liệu. Hệ quả thực dụng:
- Bảng đọc nhiều ghi ít (sản phẩm, bài viết): index thoải mái.
- Bảng ghi dồn dập (log, tracking): index tối thiểu, cân nhắc từng cái.
- Định kỳ soi index mồ côi: index không query nào dùng chỉ còn lại phần thuế, cả Postgres (pg_stat_user_indexes) lẫn MySQL (sys.schema_unused_indexes) đều có sổ theo dõi.
Đặt index đúng chỗ: bộ quy tắc 5 dòng
- Cột trong WHERE lặp đi lặp lại → index.
- Cột JOIN hai bảng (khóa ngoại) → index cả hai phía.
- ORDER BY/GROUP BY trên bảng lớn → đưa cột vào đuôi index kép để khỏi filesort.
- Index kép (a,b) phục vụ lọc theo a và theo a+b, nhưng KHÔNG phục vụ lọc mỗi b (như danh bạ xếp theo Họ rồi Tên: tìm theo Tên không tra được). Cột lọc "chặt" nhất đứng trước.
- Cột true/false hay chỉ vài giá trị (status): index đơn thường vô ích, trừ khi đi trong index kép hoặc partial index (Postgres) cho đúng nhánh hiếm.
Index và ổ đĩa: mối quan hệ ít người nối
Query full scan bảng 2 GB = đọc 2 GB từ ổ (khi cache không chứa nổi), chiếm băng thông I/O, đá văng dữ liệu nóng khác khỏi cache, và trên ổ chậm là vài giây đứng hình cho cả máy. Query dùng index = vài chục lượt đọc nhỏ. Nghĩa là index không chỉ cứu một query, nó giảm tổng áp I/O cho mọi thứ chạy chung. Cặp bài trùng của hệ thống khỏe: index đúng (giảm số lượt đọc) + ổ NVMe (mỗi lượt đọc rẻ), thiếu vế nào, vế kia gánh gấp đôi.
Tự đo mức tăng tốc thật trên dữ liệu của bạn
# Truoc khi them chi muc: xem ke hoach va thoi gian thuc EXPLAIN ANALYZE SELECT * FROM don_hang WHERE ma_khach = 12345; # Them chi muc, khong khoa bang trong luc dang chay that CREATE INDEX CONCURRENTLY idx_don_hang_ma_khach ON don_hang (ma_khach); # Do lai EXPLAIN ANALYZE SELECT * FROM don_hang WHERE ma_khach = 12345; # Chi muc nao dang khong ai dung, va chiem bao nhieu dung luong SELECT relname, indexrelname, idx_scan, pg_size_pretty(pg_relation_size(indexrelid)) FROM pg_stat_user_indexes ORDER BY idx_scan ASC LIMIT 20;
Truy vấn cuối là truy vấn đáng chạy nhất trên hệ thống đang chạy thật. Chỉ mục có lượt dùng bằng 0 sau vài tháng là chỉ mục chỉ tốn dung lượng và làm chậm mọi lệnh ghi, xóa nó đi.
Mức tăng tốc phụ thuộc vào đâu
| Tình huống | Mức cải thiện thường thấy | Vì sao |
|---|---|---|
| Bảng lớn, điều kiện lọc chọn ra rất ít dòng | Rất lớn | Thay vì đọc cả bảng, chỉ đọc vài trang dữ liệu |
| Bảng lớn, điều kiện lọc vẫn trả về nửa số dòng | Gần như không | Quét toàn bộ bảng vẫn rẻ hơn đi qua chỉ mục |
| Bảng nhỏ, vài nghìn dòng | Không đáng kể | Cả bảng nằm sẵn trong bộ nhớ |
| Sắp xếp theo cột đã có chỉ mục | Đáng kể | Không phải sắp xếp lại |
Con số cụ thể phụ thuộc hoàn toàn vào dữ liệu của bạn. Đây là lý do phần đo ở trên quan trọng hơn mọi con số dẫn lại từ bài viết khác.
Cái giá phải trả, tính được bằng số
- Mỗi lệnh ghi phải cập nhật thêm mọi chỉ mục của bảng. Bảng có 8 chỉ mục thì mỗi lần thêm một dòng là 9 lần ghi. Với bảng ghi nhiều, đây là chi phí thật.
- Chỉ mục chiếm dung lượng, đôi khi bằng hoặc hơn chính dữ liệu. Truy vấn ở trên cho biết con số chính xác.
- Chỉ mục cũng cần nằm trong bộ nhớ mới nhanh. Quá nhiều chỉ mục đẩy dữ liệu nóng ra khỏi vùng đệm, làm chậm mọi thứ.
Bộ quy tắc thực dụng: đánh chỉ mục cho cột hay xuất hiện trong điều kiện lọc và cột nối bảng; không đánh cho cột chỉ có vài giá trị khác nhau; với điều kiện nhiều cột thì một chỉ mục ghép đúng thứ tự tốt hơn nhiều chỉ mục rời; và mỗi quý rà lại danh sách chỉ mục không ai dùng.
Câu hỏi thường gặp
Làm sao biết bảng nào đang thiếu index?
Bật slow query log (MySQL) hoặc log_min_duration_statement (Postgres), chạy tải thật vài giờ, EXPLAIN các query trong log, full scan trên bảng lớn hiện nguyên hình. Quy trình chi tiết ở bài MySQL chậm.
Primary key có phải index không?
Phải, và là index quan trọng nhất (InnoDB còn xếp dữ liệu vật lý theo nó). Unique constraint cũng tự sinh index. Nên nhiều bảng đã có sẵn 1-2 index mà bạn không nhớ đã tạo.
Có nên để AI/ORM tự đặt index không?
ORM sinh index cho khóa ngoại là khởi đầu tốt; agent AI đọc slow log rồi đề xuất index cũng ngày càng đáng tin. Nhưng phê duyệt cuối nên là người hiểu pattern truy cập, index là quyết định có thuế dài hạn.
Index có cần bảo trì không?
Postgres: autovacuum lo phần lớn, REINDEX khi phình bất thường. MySQL: hiếm khi phải đụng. Việc bảo trì thật sự là mỗi quý soi lại index không dùng và query mới chưa có index.
Bài viết liên quan
- MySQL chậm: đọc EXPLAIN và tìm nghẽn
- Tối ưu PostgreSQL: 12 tham số theo NVMe
- VPS ổ Enterprise U.2 NVMe RAID 10 tại TND
- Supabase Auth tự host: đăng nhập cho app của bạn
- Freelancer MMO Kiếm Tiền Online: Chọn Windows License Nào Cho PC/Laptop An Toàn
- Hướng dẫn cài đặt EmDash CMS Self-Hosted trên Ubuntu 24 VPS - Thay thế WordPress hoàn toàn
- Monitoring stack: Zabbix vs Grafana + Prometheus 2026
- Self-host Mailcow email server trên VPS cho startup VN
- Setup Gitea + Drone CI trên VPS: self-host CI/CD đầy đủ



