it-swarm.asia

Bekerja dengan indeks dalam PostgreSQL

Saya punya beberapa pertanyaan tentang cara kerja indeks di PostgreSQL. Saya memiliki tabel Friends dengan indeks berikut:

   Friends ( user_id1 ,user_id2) 

user_id1 dan user_id2 adalah kunci asing ke tabel user

  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?

80
codecool

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

Saya diuji dengan PostgreSQL 9.0.5 di Debian menggunakan tabel berukuran sedang dari database kehidupan nyata dengan 23322 baris. Itu mengimplementasikan hubungan n: m antara tabel adr (alamat) dan att (atribut), tetapi 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)
);

Batasan UNIQUE secara efektif mengimplementasikan indeks 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_uni indeks dan sebelum tes saya berlari:

CLUSTER adratt;
ANALYZE adratt;

Pemindaian sekuensial untuk kueri pada (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 x 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.

83

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 = 1 Tidak masalah karena biasanya hanya kolom "terkemuka" yang dapat digunakan untuk perbandingan oleh pengoptimal. Jadi where user_id1 = 1 Akan 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 di (user_id1, user_id2) (yang dapat Anda temukan sendiri sangat dengan mudah hanya dengan membuat kunci utama)

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

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

30

Iklan 1)
Ada batasan dalam PostgreSQL seperti yang dijelaskan @a_horse_with_no_name . Hingga versi 8. indeks multikolom hanya dapat digunakan untuk kueri pada kolom utama. Ini telah ditingkatkan di versi 8.1. The manual saat ini untuk Postgres 1 (diperbarui) menjelaskan:

Indeks B-tree multikolom dapat digunakan dengan kondisi kueri yang melibatkan subset kolom indeks mana pun, tetapi indeks ini paling efisien bila ada kendala pada kolom terkemuka (paling kiri). Aturan yang tepat adalah bahwa kendala kesetaraan pada kolom terkemuka, 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, diberi indeks pada (a, b, c) dan kondisi permintaan WHERE a = 5 AND b >= 42 AND c < 77, indeks harus dipindai dari entri pertama dengan a = 5 dan b = 42 naik melalui entri terakhir dengan a = 5. Indeks entri dengan c> = 77 akan dilewati, tetapi mereka masih harus dipindai. Indeks ini pada prinsipnya dapat digunakan untuk kueri yang memiliki kendala pada b dan/atau c tanpa kendala pada a - tetapi seluruh indeks harus dipindai, jadi dalam kebanyakan kasus perencana lebih suka pemindaian tabel berurutan daripada menggunakan indeks.

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

12

Ini sebagai balasan untuk jawaban Jack , komentar tidak akan dilakukan.

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

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

Indeks true covering (pembaruan) diperkenalkan dengan klausa INCLUDE 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 besar, tetapi yang tentunya 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 "scan hanya indeks" dalam hal 9.2 atau lebih baru.

12
  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 bentuk select * from foo where baz=?

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

Ringkasan: indeks dapat membantu permintaan bahkan pada kolom yang tidak terkemuka, tetapi dalam salah satu dari dua cara sekunder dan relatif kecil dan tidak dengan cara dramatis Anda biasanya mengharapkan indeks untuk membantu karena itu struktur btree

nb dua cara indeks dapat membantu adalah jika pemindaian penuh indeks secara signifikan lebih murah daripada pemindaian penuh tabel dan baik: 1. pencarian tabel murah (karena ada beberapa dari mereka atau mereka mengelompok), atau 2. indeksnya meliputi 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, memukul 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 adalah 'sedikit lebih cepat' !, dan YMMV tergantung pada ukuran relatif tabel dan indeks, bersama dengan jumlah baris yang disaring dan karakteristik pengelompokan data dalam tabel

Sebaliknya, pertanyaan pada kolom terkemuka menggunakan struktur btree dari indeks - dalam hal ini memukul 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