- Chỉ mục PostgreSQL là cấu trúc cốt lõi giúp tăng tốc độ truy cập dữ liệu, bằng cách giảm lượng dữ liệu phải đọc từ đĩa để cải thiện hiệu năng truy vấn
- Chỉ mục được cung cấp dưới nhiều dạng như Btree, Hash, BRIN, GIN, GiST, SP-GiST, mỗi loại được tối ưu cho các đặc tính dữ liệu và mẫu truy vấn khác nhau
- Chỉ mục đi kèm nhiều chi phí như không gian đĩa, hiệu năng ghi, độ phức tạp của query planner, mức sử dụng bộ nhớ
- Các tính năng nâng cao như partial index, chỉ mục đa cột, covering index, expression index có thể tối đa hóa hiệu quả trong những tình huống cụ thể
- Việc lựa chọn và quản lý chỉ mục phù hợp được nhấn mạnh là yếu tố then chốt của tối ưu hiệu năng PostgreSQL
Khái niệm cơ bản về chỉ mục
- Chỉ mục là cấu trúc giúp cơ sở dữ liệu giảm lượng dữ liệu phải đọc từ đĩa để tăng tốc độ truy vấn
- Khóa chính, khóa duy nhất, ràng buộc loại trừ cũng được triển khai thông qua chỉ mục
- Chỉ mục hiệu quả khi kết quả truy vấn chiếm dưới 15~20% toàn bộ bảng; nếu cao hơn, quét tuần tự có thể hiệu quả hơn
- PostgreSQL mặc định cung cấp 6 loại chỉ mục, và có thể dùng thêm nhiều loại khác thông qua extension
- Mỗi chỉ mục liên kết giá trị khóa với vị trí dữ liệu tương ứng (TID)
Cấu trúc dữ liệu được lưu trên đĩa
- Bảng trong PostgreSQL được lưu dưới dạng tệp heap, gồm các trang 8KB
- Mỗi hàng (tuple) được lưu không theo thứ tự cụ thể, và địa chỉ nội bộ được nhận diện bằng ctid (current tuple id)
- Ví dụ:
(0,1) nghĩa là tuple đầu tiên trên trang 0
- Chỉ mục liên kết các vị trí này trong heap (ctid) theo cấu trúc cây để hỗ trợ tìm kiếm nhanh
Cách chỉ mục tăng tốc truy cập dữ liệu
- Khi không có chỉ mục, PostgreSQL thực hiện quét tuần tự bằng cách đọc tất cả các trang
- Trong ví dụ truy vấn tìm
name='Ronaldo', hệ thống đọc 6272 trang và mất 265ms
- Khi thêm chỉ mục, truy vấn chuyển sang Index Scan, chỉ đọc 4 trang và hoàn tất trong 0.077ms
- Chỉ mục ánh xạ giá trị với ctid để nhanh chóng tìm đúng hàng cần thiết
- Kích thước tệp chỉ mục có thể tương đương kích thước bảng (ví dụ: bảng 30MB → chỉ mục 30MB)
Các yếu tố chi phí của chỉ mục
- Ngoài việc cải thiện hiệu năng, chỉ mục còn đi kèm nhiều gánh nặng
Không gian đĩa
- Chỉ mục chiếm không gian lưu trữ riêng và có thể lớn hơn cả bảng
- Phát sinh thêm chi phí khi sao lưu, sao chép và khôi phục sau sự cố
- Có thể cải thiện hiệu quả không gian bằng partial index, chỉ mục đa cột, BRIN
Tác vụ ghi
- Khi
UPDATE, INSERT, DELETE, nếu cột có chỉ mục bị thay đổi thì sẽ phát sinh overhead cập nhật chỉ mục
Query planner
- Càng nhiều chỉ mục thì planner càng phải xem xét nhiều phương án, làm tăng thời gian lập kế hoạch truy vấn
Mức sử dụng bộ nhớ
- Các trang chỉ mục được nạp vào shared buffer để cache, nên càng nhiều chỉ mục thì áp lực bộ nhớ càng tăng
- Do giới hạn kích thước node btree, cột càng lớn thì độ sâu của cây càng tăng
- Trong các tác vụ như sắp xếp, quét đa cột, vacuum, reindex, work memory cũng được sử dụng thêm
Các loại chỉ mục chính
Btree
- Đây là cấu trúc chỉ mục mặc định của PostgreSQL, là chỉ mục đa dụng được dùng trong hầu hết DBMS
- Hỗ trợ tìm kiếm nhanh với độ phức tạp thời gian O(log n)
- Là cấu trúc cây cân bằng với mọi node lá có cùng độ sâu
- Phù hợp cho các phép ORDER BY, JOIN, và được dùng cho ràng buộc khóa chính·khóa duy nhất
- Node bên trong lưu con trỏ tới node con, còn node lá lưu khóa và con trỏ tới heap
- Có thể duyệt hai chiều nhờ con trỏ node trái·phải
Sử dụng nhiều chỉ mục
- PostgreSQL có thể kết hợp nhiều chỉ mục bằng phép toán bitmap AND/OR để xử lý điều kiện phức hợp
- Ví dụ: với điều kiện
age=30 AND login_count=100, hệ thống kết hợp bitmap của hai chỉ mục
Chỉ mục đa cột
- Có thể gộp nhiều cột vào một chỉ mục để tiết kiệm không gian và tăng tốc độ
- Tuy nhiên, thứ tự cột rất quan trọng, và chỉ các điều kiện khớp từ bên trái mới có thể dùng chỉ mục
Partial index
- Dùng biểu thức điều kiện để chỉ lập chỉ mục cho một phần hàng
- Giúp giảm kích thước chỉ mục, tăng khả năng nằm vừa trong RAM và cải thiện tốc độ truy vấn
- Ví dụ:
create index on rules(status) where status='enabled';
- Hữu ích khi phân bố giá trị mất cân bằng (
status <> 'TODO' v.v.)
Covering index
- Nếu mọi cột cần cho truy vấn đều nằm trong chỉ mục, có thể trả kết quả mà không cần truy cập heap (index-only scan)
create index abc_cov_idx on bar(a, b) including c;
- Hiệu quả không gian hơn chỉ mục đa cột
Expression index
- Lập chỉ mục cho kết quả của hàm hoặc biểu thức, thay vì giá trị cột gốc
- Ví dụ:
CREATE INDEX idx_lower_name ON customers (lower(name));
- Hữu ích khi tìm kiếm bằng giá trị đã biến đổi như
LOWER(name)
- Chỉ có thể dùng hàm immutable
Hash
- Là chỉ mục dựa trên cấu trúc hashmap, hiệu quả về không gian với chuỗi dài hoặc UUID
- Lưu mã băm 32-bit để giảm kích thước
- Chỉ hỗ trợ phép so sánh bằng (=), không hỗ trợ sắp xếp hay chỉ mục đa cột
- Khi phân bố băm đồng đều, có thể cho hiệu năng đọc nhanh hơn Btree
- Theo tài liệu chính thức, hash index giúp giảm I/O trên bảng lớn nhờ truy cập trực tiếp vào bucket page
BRIN (Block Range Index)
- Là chỉ mục chỉ lưu giá trị nhỏ nhất·lớn nhất của từng dải block
- Rất gọn nhẹ và thân thiện với cache
- Phù hợp với dữ liệu quy mô lớn, append-only, chuỗi thời gian
- Nếu hàng được cập nhật thường xuyên, hiệu quả sẽ giảm do lưu trùng phát sinh từ MVCC
- Có thể điều chỉnh trade-off giữa độ chính xác và kích thước bằng thiết lập
pages_per_range
GIN (Generalized Inverted Index)
- Là chỉ mục phù hợp cho tìm kiếm trên dữ liệu phức hợp
- Hỗ trợ tìm kiếm phần tử cụ thể trong văn bản, mảng, JSONB
- Sử dụng chiến lược chuyên biệt (opclass) theo từng kiểu dữ liệu
- Với JSON nên dùng cột JSONB, còn văn bản nên dùng cùng tsvector hoặc extension pg_trgm
GiST & SP-GiST
- Cây tìm kiếm tổng quát (GiST) và cây phân hoạch không gian (SP-GiST) là framework triển khai chỉ mục cho các kiểu dữ liệu cụ thể
- GiST là cây cân bằng, còn SP-GiST hỗ trợ cấu trúc không cân bằng
- Được dùng cho dữ liệu địa lý, inet, khoảng giá trị, vector văn bản
- GIN cho truy vấn nhanh, còn GiST có chi phí xây dựng·bảo trì thấp hơn
- Khi tìm kiếm toàn văn, có thể chọn một trong hai tùy yêu cầu
Kết luận
- Chỉ mục là trung tâm của tối ưu hiệu năng PostgreSQL, và điều quan trọng là cân bằng giữa tốc độ đọc với chi phí ghi·lưu trữ
- Chọn đúng loại chỉ mục theo đặc tính dữ liệu và mẫu truy vấn sẽ giúp vận hành cơ sở dữ liệu nhanh và hiệu quả hơn
- Thiết kế chỉ mục phù hợp là yếu tố thiết yếu để đảm bảo khả năng mở rộng và độ ổn định của các hệ thống lớn
1 bình luận
Ý kiến trên Hacker News
Tài liệu chính thức của PostgreSQL được viết rất tốt và cũng rất cuốn hút khi đọc nên chia sẻ lại
Tài liệu giới thiệu về PostgreSQL Indexes
Phần về chỉ mục đa cột gần như giống hệt với cách tôi đã được học
Nhưng tôi đã tự hỏi liệu điều đó trong các phiên bản PostgreSQL mới nhất có còn đúng hay không
Trước đây tôi từng thấy bitmap index scan được dùng trong một truy vấn tương tự ví dụ thứ ba, và từ đó bắt đầu nghĩ lại về “định luật” cũ
Nhân tiện, về chỉ mục thì tôi nghĩ trang web và cuốn sách Use The Index, Luke là tài liệu kinh điển mà cả đội nên đọc
Ở các phiên bản trước cũng làm được, nhưng khi đó cần quét toàn bộ chỉ mục nên kém hiệu quả
Video liên quan: liên kết YouTube
Tôi nghĩ sẽ rất tốt nếu PostgreSQL hỗ trợ mặc định incremental view maintenance
Đây là khái niệm tự động cập nhật khi dữ liệu gốc thay đổi giống như chỉ mục, nhưng không bị giới hạn ở một view cụ thể mà có thể áp dụng cho các view tùy ý
Có nhiều dự án liên quan như Noria, Materialize, Apache Flink, GCP Continuous Queries, Spark Streaming Tables, Delta Tables, ClickHouse streaming tables, TimescaleDB, ksqlDB, StreamSQL
Trong PostgreSQL, gần đây một extension tên là pg_ivm đã bắt đầu xử lý vấn đề này
Cuộc thảo luận về B-tree vs Hash index rất thú vị
Nhiều người nghĩ cột ID thì hash sẽ tốt hơn, nhưng trên thực tế B-tree mặc định lại hiệu quả hơn
Đặc biệt với việc chèn các giá trị gần như tuần tự, cấu trúc cây có lợi thế hơn
Tuy nhiên, trong bài blog được nhắc lần này thì ngược lại, hash đã thắng trong benchmark
Thời điểm bài viết này xuất hiện rất đúng lúc
Quy tắc cột dẫn đầu của chỉ mục đa cột lúc nào cũng dễ gây nhầm lẫn, nhưng nhờ bitmap index scan mà nó không còn nghiêm trọng như trước
Tính năng skip scan của PostgreSQL 18 thay đổi đáng kể hiểu biết trước đây, nên nếu bạn học theo các phiên bản cũ thì cần cập nhật mô hình tư duy
Tôi nghĩ đây là một bài viết thật sự rất hay làm tài liệu cho PostgreSQL
Về chỉ mục B-tree, từ lâu tôi đã thường xuyên tham khảo Use The Index, Luke
Tôi nghĩ đây là nội dung phải đọc
Nó vượt xa mức nhập môn đơn thuần, có chiều sâu mà vẫn đủ dễ đọc miễn là không đi vào cấu trúc nội bộ
Tôi thích phong cách viết đơn giản và khiêm tốn như thế này
Tôi thích cách truyền đạt kiến thức một cách trực tiếp