CodeGym /Các khóa học /SQL SELF /Đánh chỉ mục mảng: tạo GIN- và BTREE-indexes

Đánh chỉ mục mảng: tạo GIN- và BTREE-indexes

SQL SELF
Mức độ , Bài học
Có sẵn

Hãy tưởng tượng bạn có một bảng với hàng triệu bản ghi, và một trong các cột lưu trữ mảng. Ví dụ, mình có bảng products, và mỗi sản phẩm có thể thuộc nhiều danh mục:

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name TEXT,
    categories TEXT[] -- Mảng chuỗi để lưu danh mục sản phẩm
);

Giả sử bạn muốn tìm tất cả sản phẩm thuộc danh mục electronics. Nếu chỉ dùng toán tử @> để tìm kiếm thì có thể sẽ phải quét toàn bộ bảng:

SELECT *
FROM products 
WHERE categories @> ARRAY['electronics'];

Quét toàn bộ (Seq Scan) — chậm lắm luôn. Đặc biệt nếu bảng siêu to. Indexes sẽ giúp biến truy vấn này thành tìm kiếm nhanh hơn nhiều.

Các loại index cho mảng

PostgreSQL hỗ trợ hai loại index chính mà bạn có thể dùng cho mảng:

  1. GIN (Generalized Inverted Index) — cực kỳ hợp để tìm kiếm nhanh phần tử trong mảng hoặc kiểm tra giao nhau.
  2. BTREE (Binary Tree) — hợp cho các thao tác khác, ví dụ so sánh chính xác mảng.

Cùng tìm hiểu kỹ hơn từng loại nhé.

  1. Index GIN: nhanh như chớp

GIN (Generalized Inverted Index) — là index cực kỳ hợp với các toán tử như:

  • @> (mảng chứa phần tử hoặc mảng khác),
  • <@ (mảng nằm trong mảng khác),
  • && (hai mảng giao nhau).

Đây là cách tạo GIN-index cho cột categories của mình:

CREATE INDEX idx_categories_gin
ON products USING gin(categories);

Sau khi tạo index, truy vấn sẽ chạy nhanh hơn thấy rõ. Ví dụ truy vấn này:

SELECT *
FROM products 
WHERE categories @> ARRAY['electronics'];

sẽ dùng GIN-index của bạn.

Fun fact: GIN index hoạt động kiểu danh sách đảo ngược — nó lưu phần tử nào (ví dụ chuỗi) nằm ở bản ghi nào. Kiểu như mục lục ngược trong sách để tìm chủ đề theo số trang ấy. Quá tiện đúng không?

  1. Index BTREE: khi thứ tự quan trọng

BTREE (Binary Tree) — là index tiêu chuẩn, dùng trong hầu hết các database. Nó hợp với thao tác cần so sánh chính xác mảng, ví dụ:

  • Kiểm tra mảng bằng nhau =,
  • So sánh mảng theo thứ tự phần tử (>, <).

Tạo BTREE-index cho mảng như sau:

CREATE INDEX idx_categories_btree
ON products USING btree(categories);

Ví dụ truy vấn có thể dùng BTREE-index:

SELECT *
FROM products
WHERE categories = ARRAY['electronics', 'gadgets'];

Nhưng nhớ nhé, BTREE-index không hợp với các toán tử kiểu @> hay <@. Mấy cái này thì cứ GIN mà chiến.

Ví dụ dùng indexes

Giờ mình sẽ kết hợp lý thuyết với thực tế bằng vài ví dụ nhé.

  1. Tìm giao nhau của mảng

Giả sử bạn muốn tìm tất cả sản phẩm liên quan đến danh mục electronicssmartphones, dùng toán tử && (giao nhau của mảng):

SELECT *
FROM products
WHERE categories && ARRAY['electronics', 'smartphones'];

Vụ này thì GIN-index là chân ái, bạn đã tạo ở trên rồi:

CREATE INDEX idx_categories_gin
ON products USING gin(categories);

Có index này thì truy vấn chạy nhanh hơn nhiều nhờ danh sách đảo ngược.

  1. So sánh mảng bằng nhau

Nếu bạn cần tìm sản phẩm chỉ thuộc duy nhất các danh mục electronicsgadgets (đúng thứ tự này), thì nên dùng BTREE-index:

SELECT *
FROM products
WHERE categories = ARRAY['electronics', 'gadgets'];

Tạo index tương ứng nhé:

CREATE INDEX idx_categories_btree
ON products USING btree(categories);

Hiệu năng của indexes

Indexes giúp truy vấn nhanh hơn, nhưng cũng có mặt trái. Ví dụ:

  • Tạo index tốn thời gian và tài nguyên. Nếu bảng cực lớn, xây index có thể khá lâu đấy.
  • Cập nhật bảng. Mỗi lần bạn chèn dòng mới hoặc sửa dữ liệu, indexes cũng phải cập nhật theo. Điều này có thể làm chậm thao tác INSERTUPDATE.

Tuy nhiên, đa số trường hợp thì lợi ích từ truy vấn nhanh vượt xa mấy cái bất tiện này.

Chọn gì: GIN hay BTREE?

Dưới đây là bảng nhỏ giúp bạn chọn index hợp lý cho từng bài toán:

Loại thao tác Index khuyên dùng
Tìm giao nhau của mảng (&&) GIN
Kiểm tra bao hàm (@>, <@) GIN
Kiểm tra bằng nhau (=) BTREE
So sánh mảng (>, <) BTREE
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION