Bekerja dengan indeks dalam PostgreSQL

73

Saya punya beberapa pertanyaan tentang cara kerja indeks di PostgreSQL. Saya punya Friendstabel dengan indeks berikut:

   Friends ( user_id1 ,user_id2) 

user_id1dan user_id2kunci asing ke usermeja

  1. Apakah ini setara? Jika tidak, mengapa?

    Index(user_id1,user_id2) and Index(user_id2,user_id1)
  2. Jika saya membuat Kunci Utama (user_id1, user_id2), apakah itu secara otomatis membuat indeks untuk itu dan

    Jika indeks dalam pertanyaan pertama tidak setara, lalu indeks mana yang dibuat pada perintah kunci primer di atas?

codecool
sumber

Jawaban:

77

Berikut adalah hasil dari kueri tabel pada kolom kedua indeks multikolom .
Efeknya mudah direproduksi untuk siapa saja. Coba saja di rumah.

Saya diuji dengan PostgreSQL 9.0.5 pada Debian menggunakan tabel berukuran sedang dari database kehidupan nyata dengan 23322 baris. Itu mengimplementasikan hubungan n: m antara tabel adr(alamat) dan att(atribut), tapi itu tidak relevan di sini. Skema sederhana:

CREATE TABLE adratt (
  adratt_id serial PRIMARY KEY
, adr_id    integer NOT NULL
, att_id    integer NOT NULL
, log_up    timestamp(0) NOT NULL DEFAULT (now())::timestamp(0)
, CONSTRAINT adratt_uni UNIQUE (adr_id, att_id)
);

The UNIQUEkendala efektif menerapkan indeks yang unik. Saya mengulangi tes dengan indeks sederhana untuk memastikan dan mendapatkan hasil yang sama seperti yang diharapkan.

CREATE INDEX adratt_idx ON adratt(adr_id, att_id)

Tabel ini berkerumun di adratt_uniindeks dan sebelum tes saya berlari:

CLUSTER adratt;
ANALYZE adratt;

Pemindaian berurutan untuk kueri (adr_id, att_id)secepat mungkin. Indeks multikolom masih akan digunakan untuk kondisi kueri pada kolom indeks kedua saja.

Saya menjalankan kueri beberapa kali untuk mengisi cache dan memilih yang terbaik dari sepuluh run untuk mendapatkan hasil yang sebanding.

1. Permintaan menggunakan kedua kolom

SELECT *
FROM   adratt
WHERE  att_id = 90
AND    adr_id = 10;

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
(1 row)

Output dari EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..3.48 rows=1 width=20) (actual time=0.022..0.025 rows=1 loops=1)
  Index Cond: ((adr_id = 10) AND (att_id = 90))
Total runtime: 0.067 ms

2. Permintaan menggunakan kolom pertama

SELECT * FROM adratt WHERE adr_id = 10

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       126 |     10 |     10 | 2008-07-29 09:35:54
       125 |     10 |     13 | 2008-07-29 09:35:54
      4711 |     10 |     21 | 2008-07-29 09:35:54
     29322 |     10 |     22 | 2011-06-06 15:50:38
     29321 |     10 |     30 | 2011-06-06 15:47:17
       124 |     10 |     62 | 2008-07-29 09:35:54
     21913 |     10 |     78 | 2008-07-29 09:35:54
       123 |     10 |     90 | 2008-07-29 09:35:54
     28352 |     10 |    106 | 2010-11-22 12:37:50
(9 rows)

Output dari EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..8.23 rows=9 width=20) (actual time=0.007..0.023 rows=9 loops=1)
  Index Cond: (adr_id = 10)
Total runtime: 0.058 ms

3. Permintaan menggunakan kolom kedua

SELECT * FROM adratt WHERE att_id = 90

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
       180 |     39 |     90 | 2008-08-29 15:46:07
...
(83 rows)

Output dari EXPLAIN ANALYZE:

Index Scan using adratt_uni on adratt  (cost=0.00..818.51 rows=83 width=20) (actual time=0.014..0.694 rows=83 loops=1)
  Index Cond: (att_id = 90)
Total runtime: 0.849 ms

4. Nonaktifkan indexscan & bitmapscan

SET enable_indexscan = off;
SELECT * FROM adratt WHERE att_id = 90

Output dari EXPLAIN ANALYZE:

Bitmap Heap Scan on adratt  (cost=779.94..854.74 rows=83 width=20) (actual time=0.558..0.743 rows=83 loops=1)
  Recheck Cond: (att_id = 90)
  ->  Bitmap Index Scan on adratt_uni  (cost=0.00..779.86 rows=83 width=0) (actual time=0.544..0.544 rows=83 loops=1)
        Index Cond: (att_id = 90)
Total runtime: 0.894 ms
SET enable_bitmapscan = off
SELECT * FROM adratt WHERE att_id = 90

Output dari EXPLAIN ANALYZE:

Seq Scan on adratt  (cost=0.00..1323.10 rows=83 width=20) (actual time=0.009..2.429 rows=83 loops=1)
  Filter: (att_id = 90)
Total runtime: 2.680 ms

Kesimpulan

Seperti yang diharapkan, indeks multi-kolom digunakan untuk kueri pada kolom kedua saja.
Seperti yang diharapkan, ini kurang efektif, tetapi kueri masih 3x lebih cepat daripada tanpa indeks.
Setelah menonaktifkan pemindaian indeks, perencana kueri memilih pemindaian tumpukan bitmap, yang melakukan hampir sama cepat. Hanya setelah menonaktifkannya, itu juga kembali ke pemindaian berurutan.

Erwin Brandstetter
sumber
pengelompokan akan membuat perbedaan jika jumlah kecocokan dalam indeks cukup tinggi (lihat di sini untuk bukti - perhatikan double run untuk mendapatkan data di-cache)
Jack Douglas
1
@ JackDouglas: Saya telah memikirkan hal ini lagi. Clustering dapat membantu secara umum, karena secara efektif juga a vacuum fulldan a reindex. Selain itu akan membantu scan indeks pada pertama atau kedua kolom terkemuka banyak , tapi terluka query pada kolom kedua. Dalam tabel yang baru dikelompokkan, baris dengan nilai yang sama di kolom kedua tersebar, sehingga maksimum blok harus dibaca.
Erwin Brandstetter
28

ulang 1) Ya dan tidak.

Untuk kueri yang menggunakan kedua kolom, mis where (user_id1, user_id2) = (1,2). Tidak masalah indeks mana yang dibuat.

Untuk kueri yang memiliki kondisi hanya pada salah satu kolom mis. where user_id1 = 1Itu penting karena biasanya hanya kolom "terkemuka" yang dapat digunakan untuk perbandingan oleh pengoptimal. Jadi where user_id1 = 1akan dapat menggunakan indeks (user_id1, user_id2) tetapi tidak akan dapat indeks (user_id2, user_id1) untuk semua kasus.

Setelah bermain-main dengan ini (setelah Erwin dengan ramah menunjukkan kepada kami pengaturan tempat kerjanya), tampaknya hal ini sangat bergantung pada distribusi data kolom kedua meskipun saya belum menemukan situasi mana yang memungkinkan pengoptimal menggunakan kolom tambahan untuk kondisi MANA.

Oracle 11 yang juga dapat (kadang-kadang) menggunakan kolom yang tidak di awal definisi indeks.

re 2) Ya itu akan membuat indeks

Kutipan dari manual

Menambahkan kunci utama akan secara otomatis membuat indeks btree unik pada kolom atau grup kolom yang digunakan dalam kunci utama.

re 2a) Primary Key (user_id1,user_id2)akan membuat indeks pada (user_id1, user_id2) (yang dapat Anda temukan sendiri dengan sangat mudah hanya dengan membuat kunci utama seperti itu)

Saya sangat menyarankan Anda membaca bab tentang indeks dalam manual , itu pada dasarnya menjawab semua pertanyaan di atas.

Selain itu, Indeks apa yang akan dibuat? oleh depesz melakukan pekerjaan dengan baik menjelaskan urutan pada kolom indeks dan topik terkait indeks lainnya.

seekor kuda tanpa nama
sumber
11

Iklan 1)
Ada batasan dalam PostgreSQL seperti yang dijelaskan oleh @a_horse_with_no_name . Hingga versi 8.0 indeks multicolumn hanya dapat digunakan untuk permintaan pada kolom terkemuka. Ini telah ditingkatkan di versi 8.1. The pengguna saat ini untuk Postgres 10 (diperbaharui) menjelaskan:

Indeks multicolumn B-tree dapat digunakan dengan kondisi kueri yang melibatkan subset dari kolom indeks, tetapi indeks ini paling efisien ketika ada kendala pada kolom terkemuka (paling kiri). Aturan yang tepat adalah bahwa kendala kesetaraan pada kolom utama, ditambah kendala ketidaksetaraan pada kolom pertama yang tidak memiliki kendala kesetaraan, akan digunakan untuk membatasi bagian indeks yang dipindai. Batasan pada kolom di sebelah kanan kolom ini diperiksa dalam indeks, sehingga mereka menyimpan kunjungan ke tabel yang tepat, tetapi mereka tidak mengurangi porsi indeks yang harus dipindai. Misalnya, mengingat indeks aktif (a, b, c)dan kondisi kueri WHERE a = 5 AND b >= 42 AND c < 77, indeks harus dipindai dari entri pertama dengan a= 5 danb= 42 naik melalui entri terakhir dengan a= 5. Entri indeks dengan c> = 77 akan dilewati, tetapi mereka masih harus dipindai. Indeks ini pada prinsipnya dapat digunakan untuk permintaan yang memiliki kendala pada bdan / atau ctanpa kendala pada a- tetapi seluruh indeks harus dipindai, sehingga dalam kebanyakan kasus perencana akan lebih memilih pemindaian tabel sekuensial daripada menggunakan indeks.

Tekankan milikku. Saya dapat mengkonfirmasi itu dari pengalaman.
Lihat juga test case yang ditambahkan jawaban saya nanti di sini .

Erwin Brandstetter
sumber
11

Ini sebagai jawaban atas jawaban Jack , komentar tidak akan berlaku.

Tidak ada indeks penutup di PostgreSQL sebelum versi 9.2. Karena model MVCC, setiap tuple dalam set hasil harus dikunjungi untuk memeriksa visibilitas. Anda mungkin berpikir tentang Oracle.

Pengembang PostgreSQL berbicara tentang "hanya scan indeks" . Bahkan, fitur tersebut telah dirilis dengan Postgres 9.2. Baca pesan komit .
Depesz menulis posting Blog yang sangat informatif .

Indeks True covering (pembaruan) diperkenalkan dengan INCLUDEklausa dengan Postgres 11. Terkait:

Ini agak salah, juga:

itu bergantung pada fakta bahwa 'pemindaian penuh' dari suatu indeks seringkali lebih cepat daripada 'pemindaian penuh' dari tabel yang diindeks karena kolom tambahan dalam tabel yang tidak muncul dalam indeks.

Seperti dilaporkan dalam komentar pada jawaban saya yang lain, saya juga menjalankan tes dengan tabel dua bilangan bulat dan tidak ada yang lain. Indeks memiliki kolom yang sama dengan tabel. Ukuran indeks btree adalah sekitar 2/3 dari tabel. Tidak cukup untuk menjelaskan percepatan faktor 3. Saya menjalankan lebih banyak tes, berdasarkan pengaturan Anda, disederhanakan menjadi dua kolom dan dengan 100000 baris. Pada instalasi PostgreSQL 9.0 saya hasilnya konsisten.

Jika tabel memiliki kolom tambahan, percepatan dengan indeks menjadi lebih substansial, tetapi tentu saja bukan satu-satunya faktor di sini .

Singkatnya poin utama:

  • Indeks multi-kolom dapat digunakan dengan kueri pada kolom non-terkemuka, tetapi percepatan hanya sekitar faktor 3 untuk kriteria selektif (persentase kecil dari baris dalam hasil). Lebih tinggi untuk tupel yang lebih besar, lebih rendah untuk bagian yang lebih besar dari tabel di set hasil.

  • Buat indeks tambahan pada kolom tersebut jika kinerjanya penting.

  • Jika semua kolom yang terlibat dimasukkan dalam indeks (mencakup indeks) dan semua baris yang terlibat (per blok) dapat dilihat oleh semua transaksi, Anda bisa mendapatkan "pemindaian hanya indeks" di halaman 9.2 atau yang lebih baru.

Erwin Brandstetter
sumber
7
  1. Apakah ini setara? Jika tidak, mengapa?

    Indeks (user_id1, user_id2) dan Indeks (user_id2, user_id1)

Ini tidak setara dan secara umum indeks (bar, baz) tidak akan efisien untuk permintaan formulir select * from foo where baz=?

Erwin telah menunjukkan bahwa indeks semacam itu memang dapat mempercepat kueri, tetapi efek ini terbatas dan tidak dengan urutan yang sama seperti yang biasanya Anda harapkan indeks untuk meningkatkan pencarian - itu bergantung pada kenyataan bahwa 'pemindaian penuh' dari indeks sering lebih cepat daripada 'pemindaian penuh' dari tabel yang diindeks karena kolom tambahan dalam tabel yang tidak muncul dalam indeks.

Ringkasan: indeks dapat membantu kueri bahkan pada kolom yang tidak memimpin, tetapi dalam salah satu dari dua cara sekunder dan relatif kecil dan tidak dengan cara dramatis yang biasanya Anda harapkan akan membantu indeks karena struktur btree nya

nb dua cara indeks dapat membantu adalah jika pemindaian penuh indeks secara signifikan lebih murah daripada pemindaian penuh tabel dan baik: 1. pencarian tabel yang murah (karena ada beberapa dari mereka atau mereka mengelompok), atau 2. indeks mencakup sehingga tidak ada pencarian tabel sama sekali oops, lihat komentar Erwins di sini

testbed:

create table foo(bar integer not null, baz integer not null, qux text not null);

insert into foo(bar, baz, qux)
select random()*100, random()*100, 'some random text '||g from generate_series(1,10000) g;

kueri 1 (tanpa indeks, mencapai 74 buffer ):

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=181.41..181.42 rows=1 width=32) (actual time=3.301..3.302 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..181.30 rows=43 width=32) (actual time=0.043..3.228 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.335 ms

kueri 2 (dengan indeks - pengoptimal mengabaikan indeks - memukul 74 buffer lagi):

create index bar_baz on foo(bar, baz);

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=199.12..199.13 rows=1 width=32) (actual time=3.277..3.277 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..199.00 rows=50 width=32) (actual time=0.043..3.210 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.311 ms

kueri 2 (dengan indeks - dan kami menipu pengoptimal untuk menggunakannya):

explain (buffers, analyze, verbose) select max(qux) from foo where bar>-1000 and baz=0;
                                                       QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=115.56..115.57 rows=1 width=32) (actual time=1.495..1.495 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=36 read=30
   ->  Bitmap Heap Scan on stack.foo  (cost=73.59..115.52 rows=17 width=32) (actual time=1.370..1.428 rows=52 loops=1)
         Output: bar, baz, qux
         Recheck Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
         Buffers: shared hit=36 read=30
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..73.58 rows=17 width=0) (actual time=1.356..1.356 rows=52 loops=1)
               Index Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
               Buffers: shared read=30
 Total runtime: 1.535 ms

Jadi akses melalui indeks dua kali lebih cepat dalam hal ini mengenai 30 buffer - yang dalam hal pengindeksan 'sedikit lebih cepat' !, dan YMMV tergantung pada ukuran relatif tabel dan indeks, bersama dengan jumlah baris yang disaring dan karakteristik clustering dari data dalam tabel

Sebaliknya, pertanyaan pada kolom terkemuka menggunakan struktur btree dari indeks - dalam hal ini mengenai 2 buffer :

explain (buffers, analyze, verbose) select max(qux) from foo where bar=0;
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=75.70..75.71 rows=1 width=32) (actual time=0.172..0.173 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=38
   ->  Bitmap Heap Scan on stack.foo  (cost=4.64..75.57 rows=50 width=32) (actual time=0.036..0.097 rows=59 loops=1)
         Output: bar, baz, qux
         Recheck Cond: (foo.bar = 0)
         Buffers: shared hit=38
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..4.63 rows=50 width=0) (actual time=0.024..0.024 rows=59 loops=1)
               Index Cond: (foo.bar = 0)
               Buffers: shared hit=2
 Total runtime: 0.209 ms
Jack Douglas
sumber