Trong thế giới Postgres, các chỉ mục rất cần thiết để điều hướng hiệu quả kho lưu trữ dữ liệu dạng tab (hay còn gọi là “heap”). Postgres không duy trì phân cụm cho theheap và kiến trúc MVCC dẫn đến nhiều phiên bản của cùng một bộ điều chỉnh xung quanh. Tạo và duy trì các chỉ mục hiệu quả và hiệu quả để hỗ trợ các ứng dụng là một kỹ năng cần thiết.
Đọc tiếp để xem một số mẹo về tối ưu hóa và cải thiện việc sử dụng chỉ mục trong triển khai của bạn.
Lưu ý:Các truy vấn hiển thị bên dưới được chạy trên cơ sở dữ liệu mẫu chưa được sửa đổi.
Sử dụng Chỉ mục Bao gồm
Xem xét một truy vấn để tìm nạp email của tất cả các khách hàng không hoạt động. Khách hàng bảng có hoạt động và truy vấn được chuyển tiếp:
pagila=# EXPLAIN SELECT email FROM customer WHERE active=0;
QUERY PLAN
-----------------------------------------------------------
Seq Scan on customer (cost=0.00..16.49 rows=15 width=32)
Filter: (active = 0)
(2 rows)
Truy vấn yêu cầu quét tuần tự đầy đủ bảng khách hàng. Hãy tạo chỉ mục trên cột hiện hoạt:
pagila=# CREATE INDEX idx_cust1 ON customer(active);
CREATE INDEX
pagila=# EXPLAIN SELECT email FROM customer WHERE active=0;
QUERY PLAN
-----------------------------------------------------------------------------
Index Scan using idx_cust1 on customer (cost=0.28..12.29 rows=15 width=32)
Index Cond: (active = 0)
(2 rows)
Điều này sẽ hữu ích và quá trình quét tuần tự đã trở thành “quét chỉ mục”. Điều này có nghĩa làPostgres sẽ quét chỉ mục “idx_cust1”, sau đó tra cứu thêm trên sơ đồ của bảng để đọc các giá trị cột khác (trong trường hợp này là email cột) mà truy vấn cần.
PostgreSQL 11 đã giới thiệu các chỉ mục bao trùm. Tính năng này cho phép bạn bao gồm một hoặc nhiều cột bổ sung trong chính chỉ mục - nghĩa là giá trị của các cột bổ sung này được lưu trữ trong bộ lưu trữ dữ liệu chỉ mục.
Nếu chúng tôi sử dụng tính năng này và bao gồm giá trị của email bên trong chỉ mục, thì Postgres sẽ không cần phải xem xét đống của bảng để nhận giá trị của email . Hãy xem điều này có hiệu quả không:
pagila=# CREATE INDEX idx_cust2 ON customer(active) INCLUDE (email);
CREATE INDEX
pagila=# EXPLAIN SELECT email FROM customer WHERE active=0;
QUERY PLAN
----------------------------------------------------------------------------------
Index Only Scan using idx_cust2 on customer (cost=0.28..12.29 rows=15 width=32)
Index Cond: (active = 0)
(2 rows)
“Chỉ quét chỉ mục” cho chúng tôi biết rằng truy vấn hiện đã hoàn toàn được thỏa mãn bởi chính chỉ mục, do đó có khả năng tránh tất cả I / O của đĩa để đọc đống của bảng.
Tính đến thời điểm hiện tại, chỉ mục bao phủ chỉ có sẵn cho các chỉ mục B-Tree. Ngoài ra, chi phí duy trì một chỉ số bao trùm đương nhiên cao hơn một chỉ số thông thường.
Sử dụng chỉ mục từng phần
Chỉ mục từng phần chỉ lập chỉ mục một tập hợp con của các hàng trong bảng. Điều này giúp các chỉ mục có kích thước nhỏ hơn và quét qua nhanh hơn.
Giả sử chúng ta cần lấy danh sách email của các khách hàng ở California. Truy vấn là:
SELECT c.email FROM customer c
JOIN address a ON c.address_id = a.address_id
WHERE a.district = 'California';
trong đó có một kế hoạch truy vấn liên quan đến việc quét cả hai bảng được kết hợp:
pagila=# EXPLAIN SELECT c.email FROM customer c
pagila-# JOIN address a ON c.address_id = a.address_id
pagila-# WHERE a.district = 'California';
QUERY PLAN
----------------------------------------------------------------------
Hash Join (cost=15.65..32.22 rows=9 width=32)
Hash Cond: (c.address_id = a.address_id)
-> Seq Scan on customer c (cost=0.00..14.99 rows=599 width=34)
-> Hash (cost=15.54..15.54 rows=9 width=4)
-> Seq Scan on address a (cost=0.00..15.54 rows=9 width=4)
Filter: (district = 'California'::text)
(6 rows)
Hãy xem chỉ mục thông thường mang lại cho chúng ta những gì:
pagila=# CREATE INDEX idx_address1 ON address(district);
CREATE INDEX
pagila=# EXPLAIN SELECT c.email FROM customer c
pagila-# JOIN address a ON c.address_id = a.address_id
pagila-# WHERE a.district = 'California';
QUERY PLAN
---------------------------------------------------------------------------------------
Hash Join (cost=12.98..29.55 rows=9 width=32)
Hash Cond: (c.address_id = a.address_id)
-> Seq Scan on customer c (cost=0.00..14.99 rows=599 width=34)
-> Hash (cost=12.87..12.87 rows=9 width=4)
-> Bitmap Heap Scan on address a (cost=4.34..12.87 rows=9 width=4)
Recheck Cond: (district = 'California'::text)
-> Bitmap Index Scan on idx_address1 (cost=0.00..4.34 rows=9 width=0)
Index Cond: (district = 'California'::text)
(8 rows)
Quét địa chỉ đã được thay thế bằng quét chỉ mục trên idx_address1 và quét đống địa chỉ.
Giả sử đây là một truy vấn thường xuyên và cần được tối ưu hóa, chúng tôi có thể sử dụng chỉ mục apartial chỉ lập chỉ mục những hàng địa chỉ có quận là ‘California’:
pagila=# CREATE INDEX idx_address2 ON address(address_id) WHERE district='California';
CREATE INDEX
pagila=# EXPLAIN SELECT c.email FROM customer c
pagila-# JOIN address a ON c.address_id = a.address_id
pagila-# WHERE a.district = 'California';
QUERY PLAN
------------------------------------------------------------------------------------------------
Hash Join (cost=12.38..28.96 rows=9 width=32)
Hash Cond: (c.address_id = a.address_id)
-> Seq Scan on customer c (cost=0.00..14.99 rows=599 width=34)
-> Hash (cost=12.27..12.27 rows=9 width=4)
-> Index Only Scan using idx_address2 on address a (cost=0.14..12.27 rows=9 width=4)
(5 rows)
Giờ đây, truy vấn chỉ đọc chỉ mục idx_address2 và không chạm vào bảng địa chỉ .
Sử dụng Chỉ mục Đa giá trị
Một số cột cần lập chỉ mục có thể không có kiểu dữ liệu vô hướng. Các loại cột giống như jsonb , mảng và tsvector có tổng hợp hoặc nhiều giá trị. Nếu bạn cần lập chỉ mục các cột như vậy, thường là bạn cần tìm kiếm qua các giá trị riêng lẻ trong các cột đó.
Hãy cố gắng tìm tất cả các tựa phim bao gồm các doanh thu ở hậu trường. Bộ phim bảng có một cột mảng văn bản được gọi là đặc_nghiệp , bao gồm phần tử mảng văn bản Hậu trường nếu một bộ phim có tính năng đó. Để tìm tất cả những bộ phim như vậy, chúng tôi cần chọn tất cả các hàng có “Hậu trường” trong bất kỳ trong số các giá trị của mảng đặc_nghiệp :
SELECT title FROM film WHERE special_features @> '{"Behind The Scenes"}';
Toán tử quản lý @> kiểm tra xem bên tay trái có phải là tập hợp cao hơn của bên tay phải không.
Đây là kế hoạch truy vấn:
pagila=# EXPLAIN SELECT title FROM film
pagila-# WHERE special_features @> '{"Behind The Scenes"}';
QUERY PLAN
-----------------------------------------------------------------
Seq Scan on film (cost=0.00..67.50 rows=5 width=15)
Filter: (special_features @> '{"Behind The Scenes"}'::text[])
(2 rows)
yêu cầu quét toàn bộ đống, với chi phí là 67.
Hãy xem chỉ mục B-Tree thông thường có giúp ích gì không:
pagila=# CREATE INDEX idx_film1 ON film(special_features);
CREATE INDEX
pagila=# EXPLAIN SELECT title FROM film
pagila-# WHERE special_features @> '{"Behind The Scenes"}';
QUERY PLAN
-----------------------------------------------------------------
Seq Scan on film (cost=0.00..67.50 rows=5 width=15)
Filter: (special_features @> '{"Behind The Scenes"}'::text[])
(2 rows)
Chỉ số thậm chí không được xem xét. Chỉ mục B-Tree không có ý tưởng rằng có các yếu tố riêng lẻ trong giá trị mà nó đã lập chỉ mục.
Những gì chúng ta cần là chỉ số GIN.
pagila=# CREATE INDEX idx_film2 ON film USING GIN(special_features);
CREATE INDEX
pagila=# EXPLAIN SELECT title FROM film
pagila-# WHERE special_features @> '{"Behind The Scenes"}';
QUERY PLAN
---------------------------------------------------------------------------
Bitmap Heap Scan on film (cost=8.04..23.58 rows=5 width=15)
Recheck Cond: (special_features @> '{"Behind The Scenes"}'::text[])
-> Bitmap Index Scan on idx_film2 (cost=0.00..8.04 rows=5 width=0)
Index Cond: (special_features @> '{"Behind The Scenes"}'::text[])
(4 rows)
Chỉ mục GIN có thể hỗ trợ đối sánh giá trị riêng lẻ với giá trị tổng hợp được lập chỉ mục, dẫn đến kế hoạch truy vấn với chi phí thấp hơn một nửa so với giá trị ban đầu.
Loại bỏ các chỉ mục trùng lặp
Theo thời gian, các chỉ mục tích lũy và đôi khi một chỉ mục được thêm vào có định nghĩa tên chính xác như một chỉ mục khác. Bạn có thể sử dụng chế độ xem danh mục pg_indexes
bỏ qua các định nghĩa SQL mà con người có thể đọc được về các chỉ mục. Bạn cũng có thể dễ dàng phát hiện các định nghĩa cụ thể:
SELECT array_agg(indexname) AS indexes, replace(indexdef, indexname, '') AS defn
FROM pg_indexes
GROUP BY defn
HAVING count(*) > 1;
Và đây là kết quả khi chạy trên cơ sở dữ liệu của trang chứng khoán:
pagila=# SELECT array_agg(indexname) AS indexes, replace(indexdef, indexname, '') AS defn
pagila-# FROM pg_indexes
pagila-# GROUP BY defn
pagila-# HAVING count(*) > 1;
indexes | defn
------------------------------------------------------------------------+------------------------------------------------------------------
{payment_p2017_01_customer_id_idx,idx_fk_payment_p2017_01_customer_id} | CREATE INDEX ON public.payment_p2017_01 USING btree (customer_id
{payment_p2017_02_customer_id_idx,idx_fk_payment_p2017_02_customer_id} | CREATE INDEX ON public.payment_p2017_02 USING btree (customer_id
{payment_p2017_03_customer_id_idx,idx_fk_payment_p2017_03_customer_id} | CREATE INDEX ON public.payment_p2017_03 USING btree (customer_id
{idx_fk_payment_p2017_04_customer_id,payment_p2017_04_customer_id_idx} | CREATE INDEX ON public.payment_p2017_04 USING btree (customer_id
{payment_p2017_05_customer_id_idx,idx_fk_payment_p2017_05_customer_id} | CREATE INDEX ON public.payment_p2017_05 USING btree (customer_id
{idx_fk_payment_p2017_06_customer_id,payment_p2017_06_customer_id_idx} | CREATE INDEX ON public.payment_p2017_06 USING btree (customer_id
(6 rows)
Chỉ mục siêu cấp
Cũng có thể bạn kết thúc với nhiều chỉ mục trong đó một chỉ mục lập chỉ mục tập hợp các cột mà người kia lập chỉ mục. Điều này có thể mong muốn hoặc có thể không mong muốn - tập hợp sẵn có thể dẫn đến việc quét chỉ mục là một điều tốt, nhưng có thể chiếm quá nhiều dung lượng hoặc có thể truy vấn ban đầu được sử dụng để tối ưu hóa không còn được sử dụng nữa.
Nếu bạn muốn tự động hóa việc phát hiện các chỉ mục như vậy, thì pg_catalog tablepg_index là điểm khởi đầu tốt.
Chỉ mục không được sử dụng
Khi các ứng dụng sử dụng cơ sở dữ liệu phát triển, các truy vấn mà chúng sử dụng cũng vậy. Các chỉ mục đã được thêm trước đó có thể không còn được sử dụng bởi bất kỳ truy vấn nào nữa. Mỗi khi quét chỉ mục, người quản lý thống kê sẽ ghi nhận chỉ mục đó và số tích lũy có sẵn trong chế độ xem danh mục hệ thống pg_stat_user_indexes
dưới dạng giá trị idx_scan
. Việc theo dõi giá trị này trong một khoảng thời gian (chẳng hạn như một tháng) sẽ giúp bạn biết được chỉ mục nào không được sử dụng và có thể bị xóa.
Đây là truy vấn để lấy số lần quét hiện tại cho tất cả các chỉ mục trong ‘public’schema:
SELECT relname, indexrelname, idx_scan
FROM pg_catalog.pg_stat_user_indexes
WHERE schemaname = 'public';
với đầu ra như thế này:
pagila=# SELECT relname, indexrelname, idx_scan
pagila-# FROM pg_catalog.pg_stat_user_indexes
pagila-# WHERE schemaname = 'public'
pagila-# LIMIT 10;
relname | indexrelname | idx_scan
---------------+--------------------+----------
customer | customer_pkey | 32093
actor | actor_pkey | 5462
address | address_pkey | 660
category | category_pkey | 1000
city | city_pkey | 609
country | country_pkey | 604
film_actor | film_actor_pkey | 0
film_category | film_category_pkey | 0
film | film_pkey | 11043
inventory | inventory_pkey | 16048
(10 rows)
Tạo lại chỉ mục với ít khóa hơn
Không có gì lạ khi các chỉ mục cần được tạo lại. Các chỉ mục cũng có thể được phân loại và việc tạo lại chỉ mục có thể khắc phục điều đó, khiến nó trở nên quét nhanh hơn. Các chỉ mục cũng có thể bị hỏng. Việc thay đổi các tham số chỉ mục cũng có thể cần tạo chỉ mục.
Bật tạo chỉ mục Paralell
Trong PostgreSQL 11, việc tạo chỉ mục B-Tree diễn ra đồng thời. Nó có thể sử dụng nhiều công nhân song song để tăng tốc độ tạo chỉ mục. Tuy nhiên, bạn cần đảm bảo rằng các mục cấu hình này được đặt phù hợp:
SET max_parallel_workers = 32;
SET max_parallel_maintenance_workers = 16;
Các giá trị mặc định nhỏ một cách bất hợp lý. Lý tưởng nhất là những con số này nên tăng lên cùng với số lõi CPU. Xem tài liệu để biết thêm thông tin.
Tạo chỉ mục trong nền
Bạn cũng có thể tạo chỉ mục trong nền bằng cách sử dụng HIỆN TẠI tham số của CREATE INDEX lệnh:
pagila=# CREATE INDEX CONCURRENTLY idx_address1 ON address(district);
CREATE INDEX
Điều này khác với việc tạo chỉ mục thông thường ở chỗ nó không yêu cầu khóa bảng và do đó không khóa các lần ghi. Mặt khác, cần nhiều thời gian và tài nguyên hơn để hoàn thành.