PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Mendapatkan Hasil Maksimal Dari Indeks PostgreSQL Anda

Di dunia Postgres, indeks sangat penting untuk menavigasi penyimpanan tabledata secara efisien (alias "heap"). Postgres tidak mempertahankan pengelompokan untuk heap, dan arsitektur MVCC mengarah ke beberapa versi dari tuplelying yang sama. Membuat dan memelihara indeks yang efektif dan efisien untuk mendukung aplikasi adalah keterampilan yang penting.

Baca terus untuk mengetahui beberapa tips tentang mengoptimalkan dan meningkatkan penggunaan indeks dalam penerapan Anda.

Catatan:Kueri yang ditampilkan di bawah dijalankan pada database sampel pagila yang tidak dimodifikasi.

Gunakan Indeks Penutup

Pertimbangkan kueri untuk mengambil email dari semua pelanggan yang tidak aktif. pelanggan tabel memiliki aktif kolom, dan kuerinya langsung:

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)

Kueri meminta pemindaian berurutan penuh dari tabel pelanggan. Mari kita buat indeks pada kolom aktif:

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)

Ini membantu, dan pemindaian berurutan telah menjadi "pemindaian indeks". Ini berartiPostgres akan memindai indeks “idx_cust1”, dan kemudian mencari lebih lanjut tumpukan tabel untuk membaca nilai kolom lainnya (dalam hal ini, email kolom) yang dibutuhkan permintaan.

PostgreSQL 11 memperkenalkan indeks penutup. Fitur ini memungkinkan Anda untuk memasukkan satu atau lebih kolom tambahan dalam indeks itu sendiri – yaitu, nilai kolom tambahan ini disimpan dalam penyimpanan data indeks.

Jika kita menggunakan fitur ini dan memasukkan nilai email di dalam indeks, Postgres tidak perlu melihat ke tumpukan tabel untuk mendapatkan nilaiemail . Mari kita lihat apakah ini berhasil:

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)

"Pemindaian Hanya Indeks" memberi tahu kita bahwa kueri sekarang sepenuhnya dipenuhi oleh indeks itu sendiri, sehingga berpotensi menghindari semua I/O disk untuk membaca tumpukan tabel.

Indeks penutup hanya tersedia untuk indeks B-Tree untuk saat ini. Selain itu, biaya untuk mempertahankan indeks penutup secara alami lebih tinggi daripada yang biasa.

Gunakan Indeks Parsial

Indeks parsial hanya mengindeks subset dari baris dalam tabel. Ini membuat ukuran indeks lebih kecil dan lebih cepat untuk dipindai.

Asumsikan kita perlu mendapatkan daftar email pelanggan yang berlokasi di California. Permintaannya adalah:

SELECT c.email FROM customer c
JOIN address a ON c.address_id = a.address_id
WHERE a.district = 'California';

yang memiliki rencana kueri yang melibatkan pemindaian kedua tabel yang digabungkan:

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)

Mari kita lihat apa yang didapat dari indeks reguler:

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)

Pemindaian alamat telah diganti dengan pemindaian indeks atas idx_address1 , dan pemindaian tumpukan alamat.

Dengan asumsi ini adalah kueri yang sering dan perlu dioptimalkan, kita dapat menggunakan indeks terpisah yang hanya mengindeks baris alamat di mana distriknya adalah '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)

Kueri sekarang hanya membaca indeks idx_address2 dan tidak menyentuh tabelalamat .

Gunakan Indeks Multi-Nilai

Beberapa kolom yang memerlukan pengindeksan mungkin tidak memiliki tipe data skalar. Jenis kolom seperti jsonb , array dan vektor ts memiliki nilai komposit atau ganda. Jika Anda perlu mengindeks kolom seperti itu, biasanya Anda perlu menelusuri nilai individual di kolom tersebut juga.

Mari kita coba temukan semua judul film yang menyertakan cuplikan di balik layar. Film tabel memiliki kolom larik teks yang disebut fitur_khusus , yang menyertakan elemen larik teks Behind The Scenes jika sebuah film memiliki fitur itu. Untuk menemukan semua film seperti itu, kita perlu memilih semua baris yang memiliki "Behind The Scenes" diapa saja dari nilai larik fitur_khusus :

SELECT title FROM film WHERE special_features @> '{"Behind The Scenes"}';

Operator penahanan @> memeriksa apakah ruas kiri adalah superset dari ruas kanan.

Berikut adalah rencana kueri:

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)

yang memerlukan pemindaian heap penuh, dengan biaya 67.

Mari kita lihat apakah indeks B-Tree biasa membantu:

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)

Indeks bahkan tidak dipertimbangkan. Indeks B-Tree tidak mengetahui bahwa ada elemen individual dalam nilai yang diindeksnya.

Yang kita butuhkan adalah indeks 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)

Indeks GIN mampu mendukung pencocokan nilai individual terhadap nilai komposit yang diindeks, menghasilkan rencana kueri dengan biaya kurang dari setengah dari aslinya.

Hilangkan Indeks Duplikat

Seiring waktu, indeks terakumulasi, dan terkadang ada yang ditambahkan yang memiliki definisi yang sama persis dengan yang lain. Anda dapat menggunakan tampilan katalog pg_indexes untuk mendapatkan definisi indeks SQL yang dapat dibaca manusia. Anda juga dapat dengan mudah mendeteksi definisi yang identik:

  SELECT array_agg(indexname) AS indexes, replace(indexdef, indexname, '') AS defn
    FROM pg_indexes
GROUP BY defn
  HAVING count(*) > 1;

Dan inilah hasilnya ketika dijalankan di database stock pagila:

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)

Indeks Superset

Mungkin juga Anda berakhir dengan beberapa indeks di mana satu mengindeks superset kolom yang lainnya. Ini mungkin atau mungkin tidak diinginkan – superset dapat menghasilkan pemindaian indeks saja yang merupakan hal yang baik, tetapi mungkin memakan terlalu banyak ruang, atau mungkin kueri yang awalnya dimaksudkan untuk dioptimalkan tidak lagi digunakan.

Jika Anda ingin mengotomatiskan pendeteksian indeks tersebut, pg_catalog tablepg_index adalah titik awal yang baik.

Indeks yang Tidak Digunakan

Seiring berkembangnya aplikasi yang menggunakan basis data, demikian juga kueri yang mereka gunakan. Indeks yang ditambahkan sebelumnya tidak dapat lagi digunakan oleh kueri apa pun. Setiap kali indeks dipindai, indeks tersebut dicatat oleh manajer statistik dan jumlah akumulatif tersedia di tampilan katalog sistem pg_stat_user_indexes sebagai nilai idx_scan . Memantau nilai ini selama periode waktu tertentu (misalnya, sebulan) memberikan gambaran yang baik tentang indeks mana yang tidak digunakan dan dapat dihapus.

Berikut adalah kueri untuk mendapatkan jumlah pemindaian saat ini untuk semua indeks dalam skema 'publik':

SELECT relname, indexrelname, idx_scan
FROM   pg_catalog.pg_stat_user_indexes
WHERE  schemaname = 'public';

dengan keluaran seperti ini:

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)

Membangun Kembali Indeks Dengan Lebih Sedikit Penguncian

Tidak jarang indeks perlu dibuat ulang. Indeks juga dapat membengkak, dan membuat ulang indeks dapat memperbaikinya, menyebabkan pemindaian menjadi lebih cepat. Indeks juga bisa menjadi korup. Mengubah parameter indeks juga mungkin memerlukan pembuatan ulang indeks.

Aktifkan Pembuatan Indeks Paralell

Di PostgreSQL 11, pembuatan indeks B-Tree dilakukan bersamaan. Itu dapat menggunakan beberapa pekerja paralel untuk mempercepat pembuatan indeks. Namun, Anda perlu memastikan bahwa entri konfigurasi ini disetel dengan benar:

SET max_parallel_workers = 32;
SET max_parallel_maintenance_workers = 16;

Nilai default terlalu kecil. Idealnya, angka-angka ini harus meningkat dengan jumlah inti CPU. Lihat dokumen untuk informasi lebih lanjut.

Buat Indeks di Latar Belakang

Anda juga dapat membuat indeks di latar belakang, menggunakan SELAMANYA parameter BUAT INDEKS perintah:

pagila=# CREATE INDEX CONCURRENTLY idx_address1 ON address(district);
CREATE INDEX

Ini berbeda dengan melakukan indeks buat biasa karena tidak memerlukan penguncian di atas tabel, dan oleh karena itu tidak mengunci penulisan. Sisi negatifnya, dibutuhkan lebih banyak waktu dan sumber daya untuk menyelesaikannya.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Menyortir nilai nol setelah yang lainnya, kecuali spesial

  2. Kontrol Versi PostgreSQL dengan Atlassian Bitbucket

  3. Bagaimana Anda menemukan jumlah baris untuk semua tabel Anda di Postgres

  4. Gabungkan kueri loop bersarang ke hasil larik induk - pg-promise

  5. Bagaimana Fungsi CONCAT() Bekerja di PostgreSQL