Tìm hiểu cách chỉ mục cơ sở dữ liệu cắt giảm thời gian truy vấn, khi nào nó hữu ích (và khi nào gây hại), cùng các bước thực tế để thiết kế, kiểm tra và bảo trì chỉ mục cho ứng dụng thật.

Chỉ mục cơ sở dữ liệu là một cấu trúc tra cứu riêng giúp cơ sở dữ liệu tìm hàng nhanh hơn. Nó không phải là bản sao thứ hai của bảng. Hãy tưởng tượng giống như trang mục lục trong một cuốn sách: bạn dùng mục lục để nhảy tới chỗ gần đúng, rồi đọc trang cụ thể (hàng) bạn cần.
Không có chỉ mục, cơ sở dữ liệu thường chỉ có một lựa chọn an toàn: đọc qua nhiều hàng để kiểm tra hàng nào khớp truy vấn. Điều đó có thể ổn khi bảng chỉ có vài nghìn hàng. Khi bảng lớn lên tới hàng triệu hàng, “kiểm tra thêm hàng” chuyển thành nhiều lần đọc đĩa hơn, áp lực bộ nhớ tăng, và CPU phải làm nhiều hơn — vậy truy vấn từng cảm thấy tức thì bắt đầu chậm đi.
Chỉ mục giảm lượng dữ liệu mà cơ sở dữ liệu phải kiểm tra để trả lời các câu hỏi như “tìm đơn hàng có ID 123” hoặc “lấy người dùng có email này.” Thay vì quét mọi thứ, cơ sở dữ liệu theo cấu trúc cô đọng thu hẹp tìm kiếm nhanh chóng.
Nhưng đánh chỉ mục không phải là giải pháp toàn năng. Một số truy vấn vẫn phải xử lý nhiều hàng (báo cáo rộng, bộ lọc chọn lọc thấp, tính toán tổng hợp nặng). Và chỉ mục có chi phí thực sự: tốn thêm dung lượng lưu trữ và làm chậm ghi, vì khi chèn hoặc cập nhật phải cập nhật cả chỉ mục.
Bạn sẽ biết:
Khi cơ sở dữ liệu chạy một truy vấn, nó có hai lựa chọn rộng: quét toàn bộ bảng từng hàng, hoặc nhảy trực tiếp đến các hàng khớp. Hầu hết lợi ích của chỉ mục đến từ việc tránh đọc không cần thiết.
Một quét toàn bộ bảng là đúng như tên gọi: cơ sở dữ liệu đọc mọi hàng, kiểm tra xem nó có khớp điều kiện WHERE hay không, rồi mới trả kết quả. Điều này chấp nhận được cho bảng nhỏ, nhưng nó chậm lại theo cách có thể dự đoán khi bảng tăng—càng nhiều hàng thì càng nhiều công việc.
Dùng chỉ mục, cơ sở dữ liệu thường tránh được việc phải đọc phần lớn các hàng. Thay vào đó, nó tham khảo chỉ mục trước (một cấu trúc cô đọng được xây dựng để tìm kiếm) để biết nơi các hàng khớp nằm, rồi chỉ đọc các hàng cụ thể đó.
Hãy tưởng tượng một cuốn sách. Nếu bạn muốn mọi trang đề cập “photosynthesis”, bạn có thể đọc toàn bộ cuốn sách (quét toàn bộ). Hoặc bạn có thể dùng mục lục, nhảy tới các trang được liệt kê và chỉ đọc các phần đó (tra cứu bằng chỉ mục). Cách thứ hai nhanh hơn vì bạn bỏ qua gần như tất cả các trang.
Cơ sở dữ liệu tốn nhiều thời gian chờ đọc — đặc biệt khi dữ liệu chưa ở trong bộ nhớ. Cắt giảm số hàng (và trang) mà cơ sở dữ liệu phải chạm tới thường giảm:
Chỉ mục giúp nhiều nhất khi dữ liệu lớn và mẫu truy vấn có tính chọn lọc (ví dụ, lấy 20 hàng khớp trong 10 triệu). Nếu truy vấn trả về hầu hết các hàng, hoặc bảng nhỏ đủ để nằm gọn trong bộ nhớ, quét toàn bộ có thể nhanh tương đương — hoặc còn nhanh hơn.
Chỉ mục hoạt động vì chúng tổ chức giá trị sao cho cơ sở dữ liệu có thể nhảy tới gần nơi bạn cần thay vì kiểm tra từng hàng.
Cấu trúc chỉ mục phổ biến nhất trong SQL là B-tree (thường viết B-tree hoặc B+tree). Về mặt khái niệm:
Bởi vì có thứ tự, B-tree rất tốt cho cả tìm chính xác (WHERE email = ...) và truy vấn phạm vi (WHERE created_at >= ... AND created_at < ...). Cơ sở dữ liệu có thể điều hướng tới vùng giá trị phù hợp rồi quét tiếp theo thứ tự.
Người ta nói tra cứu B-tree là “logarithmic”. Thực tế, điều đó có nghĩa: khi bảng tăng từ hàng nghìn lên hàng triệu hàng, số bước để tìm một giá trị tăng chậm, không tỉ lệ thuận. Thay vì “gấp đôi dữ liệu thì gấp đôi công việc”, nó giống kiểu “dữ liệu tăng rất nhiều chỉ thêm vài bước điều hướng”, vì cơ sở dữ liệu đi theo các con trỏ qua vài cấp độ trong cây.
Một số engine cũng cung cấp chỉ mục hash. Chúng rất nhanh cho các phép so sánh bằng vì giá trị được chuyển thành hash và dùng để tìm mục tương ứng trực tiếp.
Đổi lại: chỉ mục hash thường không giúp cho phạm vi hoặc quét theo thứ tự, và tính khả dụng/hành vi khác nhau giữa các cơ sở dữ liệu.
PostgreSQL, MySQL/InnoDB, SQL Server và các hệ khác lưu và dùng chỉ mục khác nhau (kích thước trang, clustering, cột bao gồm, kiểm tra tầm nhìn). Nhưng khái niệm cốt lõi giống nhau: chỉ mục tạo ra một cấu trúc cô đọng, có thể điều hướng, giúp cơ sở dữ liệu xác định hàng khớp với ít công việc hơn nhiều so với quét toàn bộ bảng.
Chỉ mục không làm nhanh “SQL” nói chung — chúng làm nhanh các mẫu truy cập cụ thể. Khi chỉ mục khớp cách truy vấn lọc, join hoặc sắp xếp, cơ sở dữ liệu có thể nhảy thẳng tới các hàng liên quan thay vì đọc cả bảng.
1) Bộ lọc WHERE (đặc biệt trên cột có tính chọn lọc)
Nếu truy vấn của bạn thường thu một bảng lớn xuống một tập nhỏ hàng, chỉ mục thường là chỗ đầu tiên nên xem. Ví dụ kinh điển là tìm người dùng theo định danh.
Không có chỉ mục trên users.email, cơ sở dữ liệu có thể phải quét mọi hàng:
SELECT * FROM users WHERE email = '[email protected]';
Với chỉ mục trên email, nó có thể xác định hàng(s) khớp nhanh và dừng lại.
2) Khóa JOIN (foreign keys và các khóa được tham chiếu)
Join là nơi “những bất lợi nhỏ” biến thành chi phí lớn. Nếu bạn join orders.user_id với users.id, việc đánh chỉ mục các cột join (thường là orders.user_id và khóa chính users.id) giúp cơ sở dữ liệu ghép các hàng mà không phải quét lặp lại.
3) ORDER BY (khi bạn muốn kết quả đã sắp xếp)
Sắp xếp tốn kém khi cơ sở dữ liệu phải tập hợp nhiều hàng và sắp xếp sau đó. Nếu bạn thường chạy:
SELECT * FROM orders WHERE user_id = 42 ORDER BY created_at DESC;
một chỉ mục sắp xếp phù hợp với user_id và cột sắp xếp có thể cho phép engine đọc hàng theo thứ tự cần thiết thay vì sắp xếp một tập trung gian lớn.
4) GROUP BY (khi nhóm khớp với chỉ mục)
Grouping có thể hưởng lợi khi cơ sở dữ liệu có thể đọc dữ liệu theo thứ tự nhóm. Không phải luôn luôn, nhưng nếu bạn thường group by một cột cũng dùng để lọc (hoặc cột đó tự nhiên được cluster trong chỉ mục), engine có thể làm ít việc hơn.
Chỉ mục B-tree đặc biệt tốt cho điều kiện phạm vi — nghĩ đến ngày, giá, và truy vấn “between”:
SELECT * FROM orders
WHERE created_at >= '2025-01-01' AND created_at < '2025-02-01';
Cho dashboard, báo cáo, và màn hình “hoạt động gần đây”, mẫu này xuất hiện khắp nơi, và chỉ mục trên cột phạm vi thường mang lại cải thiện ngay lập tức.
Chủ đề đơn giản: chỉ mục giúp nhiều nhất khi chúng phản chiếu cách bạn tìm kiếm và sắp xếp. Nếu truy vấn của bạn khớp với những mẫu truy cập đó, cơ sở dữ liệu có thể thực hiện đọc có mục tiêu thay vì quét rộng.
Chỉ mục giúp nhất khi nó thu hẹp rõ rệt số hàng cơ sở dữ liệu phải chạm tới. Tính chất đó gọi là độ chọn lọc.
Độ chọn lọc cơ bản là: có bao nhiêu hàng khớp một giá trị cho trước? Cột có độ chọn lọc cao có nhiều giá trị khác nhau, nên mỗi truy vấn khớp ít hàng.
email, user_id, order_number (thường là duy nhất hoặc gần duy nhất)is_active, is_deleted, status có vài giá trị phổ biếnVới độ chọn lọc cao, chỉ mục có thể nhảy tới một tập hàng nhỏ. Với độ chọn lọc thấp, chỉ mục chỉ ra một phần lớn của bảng — vậy cơ sở dữ liệu vẫn phải đọc và lọc nhiều.
Hãy nghĩ một bảng 10 triệu hàng với cột is_deleted mà 98% là false. Một chỉ mục trên is_deleted không cứu nhiều cho:
SELECT * FROM orders WHERE is_deleted = false;
“Tập khớp” vẫn gần như toàn bộ bảng. Dùng chỉ mục lúc này còn chậm hơn quét tuần tự vì engine làm thêm công nhảy giữa mục chỉ mục và trang bảng.
Bộ lập kế hoạch truy vấn ước tính chi phí. Nếu một chỉ mục không giảm đủ công việc — vì quá nhiều hàng khớp, hoặc truy vấn cần hầu hết các cột — nó có thể chọn quét toàn bộ bảng.
Phân phối dữ liệu không cố định. Một cột status có thể bắt đầu phân bố đều, rồi trôi dần tới một giá trị chiếm đa số. Nếu thống kê không được cập nhật, planner có thể đưa ra phán đoán tồi, và một chỉ mục từng hữu ích có thể ngừng mang lại lợi ích.
Chỉ mục một cột là khởi đầu tốt, nhưng nhiều truy vấn thực tế lọc theo một cột và sắp xếp/lọc theo cột khác. Đó là lúc chỉ mục tổng hợp (multi-column) tỏa sáng: một chỉ mục có thể phục vụ nhiều phần trong truy vấn.
Hầu hết cơ sở dữ liệu (đặc biệt với B-tree) chỉ dùng hiệu quả chỉ mục tổng hợp từ các cột bên trái nhất trở đi. Hãy nghĩ chỉ mục được sắp xếp trước theo cột A, rồi trong đó theo cột B, v.v.
Điều đó có nghĩa:
account_id rồi sắp xếp/lọc theo created_atcreated_at (vì không phải là cột bên trái)Một workload phổ biến là “hiển thị các sự kiện mới nhất cho tài khoản này.” Mẫu truy vấn này:
SELECT id, created_at, type
FROM events
WHERE account_id = ?
ORDER BY created_at DESC
LIMIT 50;
thường hưởng lợi lớn từ:
CREATE INDEX events_account_created_at
ON events (account_id, created_at);
Cơ sở dữ liệu có thể nhảy thẳng tới phần chỉ mục của một account và đọc các hàng theo thứ tự thời gian, thay vì quét và sắp xếp một tập lớn.
Một chỉ mục phủ chứa tất cả các cột truy vấn cần, nên cơ sở dữ liệu có thể trả kết quả từ chỉ mục mà không tra bảng (ít đọc hơn, I/O ngẫu nhiên giảm).
Hãy cẩn trọng: thêm cột vào chỉ mục làm cho nó lớn và đắt hơn.
Chỉ mục tổng hợp rộng làm chậm ghi và tốn nhiều lưu trữ. Chỉ thêm cho các truy vấn giá trị cao cụ thể, và xác minh bằng EXPLAIN cùng đo lường thực tế trước và sau.
Chỉ mục thường được miêu tả là “tăng tốc miễn phí,” nhưng không có gì miễn phí. Cấu trúc chỉ mục phải được duy trì mỗi khi bảng thay đổi, và chúng tiêu thụ tài nguyên thực.
Khi bạn INSERT một hàng mới, cơ sở dữ liệu không chỉ ghi hàng một lần — nó còn chèn mục tương ứng vào mỗi chỉ mục trên bảng đó. UPDATE và DELETE cũng vậy.
Đó là lý do “nhiều chỉ mục” có thể làm chậm rõ rệt workload ghi. Một UPDATE chạm cột có chỉ mục có thể đặc biệt tốn: cơ sở dữ liệu phải xóa mục chỉ mục cũ và thêm mục mới (và trên một số engine, điều này có thể kích hoạt phân tách trang hoặc cân bằng lại bên trong).
Nếu ứng dụng của bạn ghi nhiều — sự kiện đơn hàng, dữ liệu cảm biến, log audit — đánh chỉ mục mọi thứ có thể khiến cơ sở dữ liệu cảm thấy ì ạch ngay cả khi đọc nhanh.
Mỗi chỉ mục chiếm không gian đĩa. Trên bảng lớn, chỉ mục có thể sánh với (hoặc lớn hơn) kích thước bảng, đặc biệt nếu bạn có nhiều chỉ mục chồng chéo.
Nó cũng ảnh hưởng tới bộ nhớ. Cơ sở dữ liệu dựa nhiều vào cache; nếu working set của bạn bao gồm vài chỉ mục lớn, cache phải chứa nhiều trang hơn để giữ nhanh. Nếu không, bạn sẽ thấy nhiều I/O đĩa hơn và hiệu suất ít đoán trước hơn.
Đánh chỉ mục là chọn cái cần tăng tốc. Nếu workload chủ yếu là đọc, nhiều chỉ mục có thể đáng giá. Nếu chủ yếu ghi, ưu tiên chỉ các chỉ mục phục vụ truy vấn quan trọng nhất và tránh trùng lặp. Một quy tắc hữu ích: chỉ thêm chỉ mục khi bạn có thể nêu rõ truy vấn mà nó giúp — và xác minh rằng lợi ích đọc vượt trội chi phí ghi và bảo trì.
Thêm chỉ mục có vẻ sẽ giúp — nhưng bạn nên chứng minh. Hai công cụ làm điều này cụ thể là kế hoạch truy vấn (EXPLAIN) và đo lường thực tế trước/sau.
Chạy EXPLAIN (hoặc EXPLAIN ANALYZE) trên chính truy vấn bạn quan tâm.
EXPLAIN ANALYZE): Nếu kế hoạch ước tính 100 hàng nhưng thực tế chạm 100.000, optimizer đã đoán sai — thường do stats lỗi thời hoặc predicate ít chọn lọc hơn mong đợi.ORDER BY, bước sort đó có thể biến mất, và đó là cải thiện lớn.Benchmark truy vấn với các tham số giống nhau, trên dữ liệu đại diện về kích thước, và ghi lại cả độ trễ và số hàng đã quét.
Cẩn thận với cache: lần chạy đầu có thể chậm hơn vì dữ liệu chưa vào bộ nhớ; các lần chạy lặp lại có thể trông “đã ổn” ngay cả khi không có chỉ mục. Để tránh tự lừa, so sánh nhiều lần chạy và tập trung vào việc kế hoạch có đổi (chỉ mục được dùng, ít hàng đọc) ngoài thời gian thô.
Nếu EXPLAIN ANALYZE cho thấy ít hàng được chạm và ít bước tốn kém (như sort), bạn đã chứng minh chỉ mục hữu ích — không phải chỉ hy vọng thế.
Bạn có thể thêm “chỉ mục đúng” nhưng vẫn không thấy tăng tốc nếu truy vấn được viết theo cách khiến cơ sở dữ liệu không thể dùng nó. Những vấn đề này thường tinh tế, vì truy vấn vẫn trả đúng kết quả — chỉ là bị ép vào kế hoạch chậm hơn.
1) Wildcard ở đầu
Khi bạn viết:
WHERE name LIKE '%term'
cơ sở dữ liệu không thể dùng B-tree bình thường để nhảy tới điểm bắt đầu, vì nó không biết “%term” bắt đầu ở đâu trong thứ tự. Nó thường phải quét nhiều hàng.
Giải pháp:
WHERE name LIKE 'term%'.2) Hàm trên cột có chỉ mục
Ví dụ trông vô hại:
WHERE LOWER(email) = '[email protected]'
Nhưng LOWER(email) thay đổi biểu thức, nên chỉ mục trên email không thể dùng trực tiếp.
Giải pháp:
WHERE email = ....LOWER(email).Ép kiểu ngầm định: So sánh khác loại dữ liệu có thể khiến DB ép kiểu một phía, vô hiệu hóa chỉ mục. Ví dụ: so sánh cột integer với literal chuỗi.
Collation/encoding không khớp: Nếu so sánh dùng collation khác với collation khi tạo chỉ mục (thường gặp với text theo locale khác nhau), optimizer có thể tránh dùng chỉ mục.
LIKE '%x')?LOWER(col), DATE(col), CAST(col)) ?EXPLAIN để xác nhận DB thực sự chọn gì chưa?Chỉ mục không phải “cài một lần rồi quên”. Theo thời gian, dữ liệu thay đổi, mẫu truy vấn dịch chuyển, và hình dạng vật lý của bảng và chỉ mục trôi dạt. Một chỉ mục được chọn tốt có thể dần kém hiệu quả — hoặc thậm chí gây hại — nếu bạn không bảo trì.
Hầu hết cơ sở dữ liệu dựa vào planner (optimizer) để chọn cách chạy truy vấn: dùng chỉ mục nào, thứ tự join ra sao, hay lookup có đáng hay không. Để đưa ra quyết định, planner dùng thống kê — tóm tắt phân bố giá trị, số hàng, và skew dữ liệu.
Khi thống kê lỗi thời, ước tính hàng có thể sai lệch. Điều đó dẫn tới lựa chọn kế hoạch tồi, như chọn một chỉ mục trả về nhiều hàng hơn mong đợi, hoặc bỏ qua chỉ mục vốn nhanh hơn.
Sửa thường xuyên: lên lịch cập nhật stats (thường gọi là “ANALYZE” hoặc tương tự). Sau tải dữ liệu lớn, xóa lớn, hoặc churn đáng kể, cập nhật stats sớm hơn.
Khi hàng được chèn, cập nhật và xóa, chỉ mục có thể tích tụ bloat (các trang thừa không còn dữ liệu hữu ích) và phân mảnh (dữ liệu phân tán tăng I/O). Kết quả là chỉ mục lớn hơn, nhiều đọc hơn, và quét chậm — đặc biệt cho truy vấn phạm vi.
Sửa thường xuyên: rebuild hoặc reorganize các chỉ mục dùng nhiều khi chúng đã lớn không tương xứng hoặc hiệu suất trôi dần. Cụ thể và tác động khác nhau theo DB, nên coi đây là thao tác có đo lường, không phải quy tắc chung.
Thiết lập giám sát cho:
Vòng phản hồi đó giúp bạn phát hiện khi cần bảo trì — và khi một chỉ mục nên được điều chỉnh hoặc xóa. Để biết thêm về xác thực cải tiến, xem phần nội dung tham khảo trong bài viết về cách chứng minh chỉ mục hữu ích với EXPLAIN và đo lường: /blog/how-to-prove-an-index-helps-explain-and-measurements.
Thêm chỉ mục nên là thay đổi có chủ ý, không phải đoán mò. Một quy trình nhẹ giữ bạn tập trung vào các cải tiến có đo lường và ngăn chặn “bùng chỉ mục”.
Bắt đầu bằng bằng chứng: logs truy vấn chậm, trace APM, hoặc phản hồi người dùng. Chọn một truy vấn vừa chậm vừa thường xuyên — một báo cáo hiếm 10 giây ít quan trọng hơn một truy vấn lookup 200 ms lặp đi lặp lại.
Ghi lại SQL chính xác và mẫu tham số (ví dụ: WHERE user_id = ? AND status = ? ORDER BY created_at DESC LIMIT 50). Khác biệt nhỏ thay đổi chỉ mục cần thiết.
Ghi lại độ trễ hiện tại (p50/p95), số hàng quét, và tác động CPU/IO. Lưu đầu ra kế hoạch hiện tại (ví dụ EXPLAIN / EXPLAIN ANALYZE) để so sánh sau.
Chọn cột khớp cách truy vấn lọc và sắp xếp. Ưu tiên chỉ mục tối thiểu làm cho kế hoạch ngừng quét phạm vi lớn.
Thử ở staging với dữ liệu có quy mô giống sản xuất. Chỉ mục có thể trông tốt trên dữ liệu nhỏ nhưng thất vọng ở quy mô.
Trên bảng lớn, dùng các tuỳ chọn online nếu hỗ trợ (ví dụ PostgreSQL CREATE INDEX CONCURRENTLY). Lên lịch thay đổi vào thời điểm ít tải nếu DB của bạn có thể khóa ghi.
Chạy lại cùng truy vấn và so sánh:
Nếu chỉ mục làm chi phí ghi tăng hoặc làm bùng bộ nhớ, xóa nó một cách sạch sẽ (ví dụ DROP INDEX CONCURRENTLY nếu có). Giữ migration có thể đảo ngược.
Trong migration hoặc ghi chú schema, viết rõ truy vấn mà chỉ mục phục vụ và chỉ số nào cải thiện. Bạn (hoặc đồng đội) sau này sẽ biết lý do tồn tại và khi nào an toàn để xóa.
Nếu bạn xây dịch vụ mới và muốn tránh “bùng chỉ mục” từ đầu, Koder.ai giúp bạn lặp nhanh vòng đầy đủ: sinh app React + Go + PostgreSQL từ chat, điều chỉnh schema/migration khi yêu cầu thay đổi, rồi xuất source khi muốn tự quản. Trong thực tế, điều đó giúp bạn từ “endpoint này chậm” tới “đây là EXPLAIN plan, chỉ mục tối thiểu, và migration có thể đảo ngược” mà không chờ pipeline truyền thống lâu.
Chỉ mục là đòn bẩy lớn, nhưng không phải nút thần kỳ “làm nhanh mọi thứ”. Đôi khi phần chậm của request xảy ra sau khi DB đã tìm ra các hàng đúng — hoặc mẫu truy vấn khiến chỉ mục không phải lựa chọn hàng đầu.
Nếu truy vấn đã dùng chỉ mục tốt mà vẫn chậm, hãy tìm các nguyên nhân sau:
OFFSET 999000 có thể chậm dù có chỉ mục. Ưu tiên phân trang theo khóa (keyset pagination), ví dụ “các hàng sau id/timestamp cuối cùng”.SELECT * hoặc trả hàng chục nghìn bản ghi có thể tắc nghẽn ở mạng, serialize JSON, hoặc xử lý ứng dụng.LIMIT hợp lý, và phân trang có chủ ý.Nếu bạn muốn chẩn đoán bottleneck sâu hơn, kết hợp quy trình này với hướng dẫn cách chứng minh chỉ mục hữu ích bằng EXPLAIN và đo lường.
Đừng đoán mò. Đo nơi thời gian được tiêu thụ (thực thi DB vs. hàng trả về vs. mã ứng dụng). Nếu DB nhanh mà API vẫn chậm, thêm chỉ mục sẽ không giúp.
Một chỉ mục cơ sở dữ liệu là một cấu trúc dữ liệu riêng (thường là B-tree) lưu trữ các giá trị của một số cột được chọn theo dạng có thể tìm kiếm và sắp xếp, kèm theo con trỏ về các hàng trong bảng. Cơ sở dữ liệu dùng nó để tránh phải đọc hầu hết bảng khi trả lời các truy vấn có tính chọn lọc.
Nó không phải là một bản sao đầy đủ của bảng, nhưng nó sao chép một số dữ liệu cột cùng metadata, nên vẫn tiêu tốn thêm dung lượng lưu trữ.
Không có chỉ mục, cơ sở dữ liệu có thể phải quét toàn bộ bảng: đọc nhiều (hoặc tất cả) các hàng và kiểm tra từng hàng với mệnh đề WHERE của bạn.
Với chỉ mục, thường có thể nhảy trực tiếp tới vị trí hàng khớp và chỉ đọc những hàng đó, giảm I/O đĩa, công việc CPU để lọc và áp lực cache.
Chỉ mục B-tree giữ các giá trị theo thứ tự và tổ chức thành các trang (pages) trỏ đến các trang khác, nên cơ sở dữ liệu có thể điều hướng nhanh đến “khu vực” giá trị cần tìm.
Đó là lý do B-tree phù hợp cho cả:
WHERE email = ...)WHERE created_at >= ... AND created_at < ...)Chỉ mục băm (hash) rất nhanh cho các phép so sánh bằng (=) vì giá trị được băm và nhảy thẳng tới bucket tương ứng.
Nhược điểm:
Trong nhiều trường hợp thực tế, B-tree vẫn là mặc định vì nó hỗ trợ nhiều mẫu truy vấn hơn.
Chỉ mục thường giúp nhất cho các mẫu truy cập sau:
WHERE có tính chọn lọc cao (ít hàng khớp)JOIN (foreign key và khóa tham chiếu)ORDER BY khớp với thứ tự trong chỉ mục (tránh phải sort)GROUP BY khi dữ liệu có thể đọc theo thứ tự nhómTính chọn lọc là “có bao nhiêu hàng khớp một giá trị cụ thể?”. Chỉ mục có hiệu quả khi predicate thu hẹp bảng lớn xuống một tập nhỏ.
Các cột có chọn lọc thấp (ví dụ is_deleted, is_active, enum nhỏ) thường khớp phần lớn bảng. Trong trường hợp đó, dùng chỉ mục có thể chậm hơn quét tuần tự vì engine vẫn phải đọc và lọc nhiều hàng.
Bộ tối ưu hóa ước tính chi phí và có thể quyết định rằng việc dùng chỉ mục không giảm đủ khối lượng công việc.
Lý do phổ biến:
Trong hầu hết các B-tree, chỉ mục được sắp xếp theo cột đầu tiên, rồi trong phạm vi đó theo cột thứ hai, v.v. Vì vậy cơ sở dữ liệu chỉ dùng hiệu quả nếu truy vấn khởi đầu từ cột ở bên trái nhất.
Ví dụ:
(account_id, created_at) rất tốt cho WHERE account_id = ? kèm lọc/sắp xếp theo thời gian.created_at (vì không phải cột bên trái).Chỉ mục phủ (covering index) chứa tất cả các cột cần thiết cho truy vấn, nên cơ sở dữ liệu có thể trả kết quả từ chính chỉ mục mà không cần tra bảng.
Lợi ích:
Chi phí:
Dùng chỉ mục phủ cho các truy vấn giá trị cao cụ thể, không phải “phòng ngừa”.
Kiểm tra hai thứ:
EXPLAIN / EXPLAIN ANALYZE và xác nhận kế hoạch thay đổi (ví dụ Seq Scan → Index Scan/Seek, ít hàng được đọc hơn, bước sort biến mất).Cũng theo dõi hiệu năng ghi, vì chỉ mục mới có thể làm chậm //.
Nếu một truy vấn trả về phần lớn bảng, lợi ích thường nhỏ.
INSERTUPDATEDELETE