Facebook Pixel

Tìm hiểu toàn diện về Index trong MySQL và PostgreSQL

14 Dec, 2024

Index (chỉ mục) trong Database là một cấu trúc dữ liệu được sử dụng để tăng tốc độ truy vấn và cải thiện hiệu suất của các thao tác tìm kiếm dữ liệu

Tìm hiểu toàn diện về Index trong MySQL và PostgreSQL

Mục Lục

Trong quản trị cơ sở dữ liệu, Index (chỉ mục) là một công cụ quen thuộc và cực kỳ hữu ích để cải thiện hiệu suất truy vấn. Một Index được thiết kế đúng cách có thể giúp truy vấn nhanh hơn đáng kể, nhưng nếu không hiểu rõ, nó có thể dẫn đến những vấn đề như tăng dung lượng lưu trữ hoặc làm chậm các thao tác ghi dữ liệu.

Bài viết này sẽ giúp bạn nắm vững kiến thức về Index trong hai hệ quản trị cơ sở dữ liệu phổ biến là MySQL và PostgreSQL, các công cụ phân tích hiệu suất của Index và những lỗi sai thường gặp khi sử dụng chúng.

1. Index là gì?

Index (chỉ mục) trong cơ sở dữ liệu SQL là một cấu trúc dữ liệu được sử dụng để tăng tốc độ truy vấn và cải thiện hiệu suất của các thao tác tìm kiếm dữ liệu. Index hoạt động tương tự như mục lục của một cuốn sách, giúp hệ thống tìm kiếm nhanh hơn thay vì phải đọc toàn bộ bảng.

Index thường được xây dựng dựa trên các cấu trúc dữ liệu tối ưu cho việc tìm kiếm. Hai cấu trúc phổ biến nhất là:

  • B-Tree (Balanced Tree): Là cấu trúc phổ biến nhất trong các hệ quản trị cơ sở dữ liệu như MySQL, PostgreSQL và SQL Server.
    • Các giá trị trong Index được sắp xếp theo thứ tự, giúp việc tìm kiếm, chèn, hoặc xóa dữ liệu diễn ra hiệu quả.
    • Mỗi node trong cây lưu trữ một khoảng giá trị, cho phép truy vấn dữ liệu trong thời gian log(n).
  • Hash Table: Sử dụng hàm băm để ánh xạ giá trị vào các ô nhớ.
    • Tối ưu cho truy vấn bằng toán tử so sánh bằng (=) nhưng không phù hợp với tìm kiếm phạm vi (range query, ví dụ: BETWEEN<>).

Khi thực hiện một truy vấn có sử dụng Index, cơ sở dữ liệu hoạt động theo các bước sau:

  1. Kiểm tra Index: Hệ thống kiểm tra xem cột được truy vấn có Index hay không. Nếu có, hệ thống sẽ sử dụng Index để giảm phạm vi tìm kiếm.
  2. Tra cứu Index: Index hoạt động như một bản đồ, xác định vị trí vật lý của record trong bảng dữ liệu. Thay vì duyệt qua toàn bộ bảng, hệ thống chỉ cần quét qua cấu trúc Index.
  3. Truy cập dữ liệu gốc: Sau khi xác định vị trí bản ghi thông qua Index, hệ thống truy cập vào bảng dữ liệu để lấy thông tin chi tiết.

2. Các loại Index phổ biến trong MySQL và PostgreSQL

2.1 Clustered Index

MySQL (InnoDB):

  • Clustered Index được tạo mặc định trên cột Primary Key.
  • Dữ liệu của bảng được lưu trữ vật lý theo thứ tự của Clustered Index.
  • Nếu bảng không có Primary Key, InnoDB sẽ tự tạo một Clustered Index dựa trên cột ẩn (hidden column) gọi là row_id.

PostgreSQL:

  • Không tự động tạo Clustered Index. Bạn cần tạo thủ công với lệnh CLUSTER để sắp xếp dữ liệu vật lý theo thứ tự của chỉ mục.

2.2 Non-Clustered Index

MySQL:

  • Non-Clustered Index lưu trữ dữ liệu chỉ mục và con trỏ trỏ tới vị trí của bản ghi thực tế trong bảng.
  • Non-Clustered Index phù hợp với các cột không thuộc Primary Key nhưng thường xuyên xuất hiện trong điều kiện truy vấn (WHEREORDER BY).

PostgreSQL:

  • Tất cả các chỉ mục trong PostgreSQL (ngoại trừ trường hợp sử dụng lệnh CLUSTER) mặc định là Non-Clustered.

2.3 Composite Index (Multi-Column Index)

MySQL và PostgreSQL:

  • Index được tạo trên nhiều cột, được sử dụng khi truy vấn có điều kiện kết hợp nhiều cột (WHERE col1 = ? AND col2 = ?).
  • Composite Index hoạt động dựa trên thứ tự cột khi tạo chỉ mục. Cột đầu tiên (leading column) rất quan trọng vì nó quyết định hiệu quả của chỉ mục.

Giả sử bạn có bảng orders với các cột sau: order_id, customer_id, status, order_date. Bạn tạo Composite Index trên hai cột customer_id và status như sau:

Sql
CREATE INDEX idx_customer_status ON orders (customer_id, status);

Dữ liệu trong index được sắp xếp theo thứ tự của cột đầu tiêncustomer_id, sau đó là cột thứ hai status, index sẽ lưu dữ liệu như sau:

Sql
customer_id | status     | Pointer (trỏ tới vị trí record)
---------------------------------------------------------
101         | completed  | Row 1
101         | completed  | Row 3
102         | pending    | Row 2
103         | completed  | Row 4

Vậy hãy xem với 3 trường hợp sau đây, Composite Index trên sẽ được sử dụng như thế nào nhé:

  1. Truy vấn 1: Sử dụng cả hai cột (customer_id và status): Truy vấn sử dụng cả customer_id và status, Composite Index được sử dụng đầy đủ.
  2. Truy vấn 2: Chỉ sử dụng customer_id: Composite Index vẫn được sử dụng vì cột đầu tiên (customer_id) là cột sắp xếp chính.
  3. Truy vấn 3: Chỉ sử dụng status: Index sẽ không được sử dụng vì status không phải là cột đầu tiên trong Composite Index. Truy vấn sẽ phải quét toàn bộ bảng (full table scan).
💡
Nếu mình tạo 2 index riêng lẻ cho 2 cột này thì sao? Trong trường hợp này, Query Optimizer sẽ cố gắng sử dụng cả hai index (nếu có thể) và kết hợp kết quả. Đây được gọi là Index Intersection, nhưng hiệu quả không cao bằng Composite Index.

2.4 Unique Index

MySQL và PostgreSQL:

  • Unique Index đảm bảo giá trị trong cột hoặc tập hợp cột là duy nhất.
  • Mặc dù Unique Index thường được sử dụng cho các cột khóa chính, bạn cũng có thể sử dụng nó cho các cột khác để tránh trùng lặp.

2.5 Full-Text Index

MySQL (InnoDB):

  • Full-Text Index được sử dụng để tìm kiếm văn bản (text search) trong các cột kiểu dữ liệu văn bản (CHARVARCHARTEXT).
  • Hỗ trợ các truy vấn như MATCH ... AGAINST để tìm kiếm nhanh trong các khối văn bản lớn.

PostgreSQL:

  • Tích hợp tính năng Full-Text Search mạnh mẽ thông qua GIN (Generalized Inverted Index).
  • Cho phép sử dụng kiểu dữ liệu tsvector và các hàm như to_tsvector và to_tsquery để tìm kiếm văn bản nhanh và chính xác.

2.6 Spatial Index

MySQL (InnoDB):

  • Dùng cho các cột kiểu dữ liệu không gian (spatial data) như GEOMETRYPOINTPOLYGON.
  • Hỗ trợ truy vấn không gian (spatial queries) với các hàm như ST_Contains hoặc ST_Within.

PostgreSQL:

  • Hỗ trợ Spatial Index thông qua extension PostGIS, giúp mở rộng khả năng lưu trữ và truy vấn dữ liệu không gian một cách hiệu quả.

2.7 Partial Index

PostgreSQL:

  • Partial Index là chỉ mục trên một tập hợp con của dữ liệu, được định nghĩa bằng một điều kiện (WHEREclause).
  • Tăng hiệu suất khi chỉ một phần nhỏ dữ liệu cần được đánh chỉ mục.

Ví dụ: Trong bảng orders, chỉ các đơn hàng completed là quan trọng cho báo cáo hoặc phân tích, trong khi các trạng thái khác ít được truy vấn.

Sql
CREATE INDEX idx_partial ON orders (customer_id) WHERE status = 'completed';

2.8 Hash Index

MySQL:

  • Chỉ hỗ trợ Hash Index trong Memory Engine. Hash Index không hỗ trợ range queries (BETWEEN><).

PostgreSQL:

  • Hash Index được hỗ trợ natively và phù hợp với các truy vấn tìm kiếm chính xác (=).
  • Tính năng này đã được cải thiện đáng kể từ PostgreSQL 10 với khả năng WAL-logging, giúp tăng tính an toàn cho dữ liệu.

2.9 GIN (Generalized Inverted Index)

PostgreSQL:

  • Dùng cho các kiểu dữ liệu mảng, JSON, hoặc Full-Text Search.
  • GIN cho phép tìm kiếm nhanh trong các phần tử của mảng hoặc các trường trong JSON.

2.10 BRIN (Block Range Index)

PostgreSQL:

  • Phù hợp với bảng lớn, lưu trữ dữ liệu dạng tuần tự như dữ liệu thời gian (timestamps) hoặc giá trị tăng dần.
  • BRIN sử dụng rất ít bộ nhớ và tối ưu hóa các truy vấn dạng range trên dữ liệu tuần tự.

3. Công cụ Phân tích hiệu suất của Index

Hiểu rõ hiệu suất của Index đóng vai trò rất quan trọng trong việc tối ưu hóa truy vấn và hệ thống cơ sở dữ liệu. Cả PostgreSQL và MySQL đều cung cấp các công cụ và kỹ thuật giúp kiểm tra, phân tích và tối ưu Index

3.1 MySQL

3.1.1 Sử dụng lệnh EXPLAIN

EXPLAIN cho biết cách MySQL xử lý một truy vấn, bao gồm việc có sử dụng Index hay không.

Sql
EXPLAIN SELECT * FROM table_name WHERE column_name = 'value';
id select_type table type key key_len ref rows Extra
1 SIMPLE orders ref idx_customer 4 const 1 Using where

Các thông tin quan trọng trả về bao gồm:

  • key: Index đang được sử dụng (VD:idx_customer).
  • rows: Số lượng record MySQL dự kiến sẽ quét.
  • type: Loại tìm kiếm:ref: Index được sử dụng cho truy vấn.ALL: Không sử dụng index, quét toàn bộ bảng (cần tối ưu hóa).range: Index được sử dụng để quét trong một phạm vi (tốt).

3.1.2 Sử dụng SHOW INDEX

Câu lệnh này liệt kê tất cả các index của một bảng, giúp bạn kiểm tra thông tin về các chỉ mục hiện có.

Sql
SHOW INDEX FROM orders;
Table Non_unique Key_name Seq_in_index Column_name Cardinality Index_type
orders 1 idx_customer 1 customer_id 1000 BTREE

Ý nghĩa:

  • Non_unique: 1 nếu index không bắt buộc duy nhất, 0 nếu là unique index.
  • Cardinality: Số lượng giá trị unique trong index (giá trị càng lớn, hiệu quả càng cao).
  • Index_type: Loại cấu trúc dữ liệu (MySQL thường sử dụng BTREE).

3.2 PostgreSQL

3.2.1 Sử dụng EXPLAIN và EXPLAIN ANALYZE

Tương tự MySQL, PostgreSQL cũng có công cụ EXPLAIN để kiểm tra kế hoạch thực thi truy vấn. EXPLAIN ANALYZE chi tiết hơn, vì nó thực hiện truy vấn thực tế và đo lường thời gian thực thi.

Sql
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 101;

Kết quả:

Text
Index Scan using idx_customer on orders  (cost=0.29..8.52 rows=1 width=48) (actual time=0.050..0.052 rows=1 loops=1)
  Index Cond: (customer_id = 101)
Planning Time: 0.125 ms
Execution Time: 0.086 ms

Các thông tin quan trọng:

  • Index Scan: PostgreSQL đang sử dụng chỉ mục.
  • cost: Ước tính chi phí của truy vấn (giá trị thấp là tốt).
  • actual time: Thời gian thực tế để thực hiện truy vấn.
  • Index Cond: Điều kiện truy vấn áp dụng trên chỉ mục.

3.2.2 Sử dụng Tablepg_stat_user_indexes

PostgreSQL cung cấp table pg_stat_user_indexes để thống kê việc sử dụng index.

Sql
SELECT
  relname AS table_name,
  indexrelname AS index_name,
  idx_scan AS index_scans,
  idx_tup_read AS tuples_read,
  idx_tup_fetch AS tuples_fetched
FROM
  pg_stat_user_indexes
WHERE
  schemaname = 'public';
table_name index_name index_scans tuples_read tuples_fetched
orders idx_customer 1000 5000 1000

Ý nghĩa:

  • index_scans: Số lần index được sử dụng.
  • tuples_read: Số record được quét qua index.
  • tuples_fetched: Số record thực sự được truy xuất.

3.2.3 Sử dụng REINDEX

Nếu index bị phân mảnh do có quá nhiều thao tácINSERT/DELETE, bạn có thể reindex với lệnh:

Sql
REINDEX INDEX idx_customer;

4. Những lỗi thường gặp khi sử dụng Index

4.1 Tạo quá nhiều Index

Nhiều developer có xu hướng tạo nhiều Index "cho chắc" mà không chắc tương lai cho dùng đến nó hay không. Mỗi Index tạo thêm đồng nghĩa với việc cơ sở dữ liệu phải tốn thêm tài nguyên để duy trì chúng.

Dẫn đến việc làm chậm các thao tác ghi nhưINSERTUPDATE, và DELETE do hệ thống phải cập nhật nhiều Index, tăng chi phí lưu trữ mà không thực sự cải thiện hiệu suất truy vấn.

Bạn nên:

  • Chỉ tạo Index trên các cột thực sự cần thiết, đặc biệt là những cột thường xuyên sử dụng trong các điều kiện WHEREJOIN, hoặc ORDER BY.
  • Kiểm tra và xóa bỏ các Index không sử dụng.
Sql
SELECT indexrelname, idx_scan FROM pg_stat_user_indexes WHERE idx_scan = 0;

4.2 Không tạo Index khi cần thiết

Bỏ qua việc tạo Index trên các cột thường xuyên được truy vấn, khiến cơ sở dữ liệu phải quét toàn bộ bảng.

Bạn nên:

  • Phân tích truy vấn bằng công cụ như EXPLAIN để xác định các cột chưa được tối ưu hóa.
  • Tạo Index trên các cột thường xuyên xuất hiện trong điều kiện lọc (WHERE) hoặc sắp xếp (ORDER BY).

4.3 Sử dụng sai loại Index

Không chọn đúng loại Index cho từng trường hợp. Ví dụ:

  • Dùng Hash Index cho các truy vấn phạm vi (BETWEEN<>).
  • Không tận dụng GIN Index khi tìm kiếm toàn văn bản trong PostgreSQL.

Bạn nên:

  • Dùng B-Tree Index cho điều kiện = và truy vấn phạm vi.
  • Dùng GIN Index hoặc Full-Text Index cho tìm kiếm văn bản.

4.4 Bỏ qua Composite Index

Chỉ tạo Index trên từng cột riêng lẻ thay vì tận dụng Composite Index (Index trên nhiều cột).

Bạn nên: Tạo Composite Index cho các truy vấn liên quan đến nhiều cột.

5. Kết luận

Việc sử dụng Index không đơn giản là "càng nhiều càng tốt" hay "tạo để đó". Đó là một nghệ thuật cân bằng: đủ để tăng hiệu suất, nhưng không dư thừa gây lãng phí tài nguyên. Một truy vấn nhanh không chỉ phụ thuộc vào Index, mà còn cần cấu trúc bảng hợp lý, câu lệnh SQL tối ưu và sự nhạy bén trong việc thiết kế hệ thống của bạn nữa đấy.

Các bài viết liên quan:

Bài viết liên quan

Lập trình backend expressjs

xây dựng hệ thống microservices
  • Kiến trúc Hexagonal và ứng dụngal font-
  • TypeScript: OOP và nguyên lý SOLIDal font-
  • Event-Driven Architecture, Queue & PubSubal font-
  • Basic scalable System Designal font-

Đăng ký nhận thông báo

Đừng bỏ lỡ những bài viết thú vị từ 200Lab