PostgreSQL'de indekslerin çalışması


73

PostgreSQL'de indekslerin çalışmasıyla ilgili birkaç sorum var. FriendsAşağıdaki dizini içeren bir tablo var :

   Friends ( user_id1 ,user_id2) 

user_id1ve tablonun user_id2yabancı anahtarlarıuser

  1. Bunlar eşdeğer mi? Öyleyse neden olmasın?

    Index(user_id1,user_id2) and Index(user_id2,user_id1)
  2. Birincil Anahtar (user_id1, user_id2) oluşturursam, otomatik olarak dizinler oluşturur ve

    İlk sorudaki dizinler eşdeğer değilse, yukarıdaki ana anahtar komutunda hangi dizin oluşturulur?

Yanıtlar:


77

Çok sütunlu bir dizinin ikinci sütununda bir tabloyu sorgulamanın sonuçları aşağıdadır .
Etkileri herkes için yeniden üretmek kolaydır. Sadece evde dene.

PostgreSQL 9.0.5 ile Debian'da, 23322 satırlık gerçek zamanlı bir veri tabanının orta büyüklükteki bir tablosunu kullanarak test ettim . Tablolar adr(adres) ve att(öznitelik) arasındaki n: m ilişkisini uygular , ancak bu burada geçerli değildir. Basitleştirilmiş şema:

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)
);

UNIQUEKısıt etkili şekilde benzersiz bir dizin uygular. Testi emin olmak için düz bir indeks ile tekrarladım ve beklendiği gibi aynı sonuçları aldım.

CREATE INDEX adratt_idx ON adratt(adr_id, att_id)

Tablo adratt_unidizinde ve çalıştırdığım testten önce kümelenmiştir :

CLUSTER adratt;
ANALYZE adratt;

Sorguları için sıralı taramalar (adr_id, att_id), olabildiğince hızlıdır. Çok sütunlu dizin, yalnızca ikinci dizin sütununda bir sorgu koşulu için kullanılacaktır.

Önbelleği doldurmak için sorguları birkaç kez çalıştırdım ve karşılaştırılabilir sonuçlar elde etmek için on çalışmadan en iyisini seçtim.

1. Her iki sütunu kullanarak sorgula

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)

Çıktı 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. İlk sütunu kullanarak sorgula

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)

Çıktı 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. İkinci sütunu kullanarak sorgula

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)

Çıktı 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. Indexscan ve bitmapscan'i devre dışı bırakın

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

EXPLAIN ANALYZE çıktısı:

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

Çıktı 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

Sonuç

Beklendiği gibi, çoklu sütun dizini yalnızca ikinci sütundaki bir sorgu için kullanılır.
Beklendiği gibi, daha az etkilidir, ancak sorgu hala indekssiz olandan 3 kat daha hızlıdır .
Dizin taramalarını devre dışı bıraktıktan sonra, sorgu planlayıcısı, neredeyse kadar hızlı olan bir bitmap yığın taraması seçer. Ancak bunu devre dışı bıraktıktan sonra, sıralı taramaya geri döner.


Kümelenme olacak endeksinde maç sayısı yeterince yüksek ise, bir fark yaratmak (bkz burada kanıtı - veri önbelleğe alma çift çalışır unutmayın)
Jack Douglas

1
@ JackDouglas: Buna biraz daha düşündüm. Kümelenme olabilir o etkili bir şekilde de, çünkü genellikle yardımcı vacuum fullve reindex. Bunun dışında, dizinlerin ilk veya her iki sütununda çok fazla tarama yapar ancak ikinci sütunda sorguları incitir . Yeni kümelenmiş bir tabloda, ikinci sütunda aynı değere sahip satırlar yayılır, böylece en fazla bloğun okunması gerekir.
Erwin Brandstetter

28

re 1) Evet ve hayır.

Her iki sütunu kullanan bir sorgu için, örneğin where (user_id1, user_id2) = (1,2)hangi dizinin yaratıldığı önemli değildir.

Sütunlardan yalnızca biri üzerinde bir koşulu olan bir sorgu için, örneğin where user_id1 = 1önemli olduğu için genellikle "öncü" sütunların en iyileştirici tarafından karşılaştırılması için kullanılabildiğinden. Böylece where user_id1 = 1indeksi (user_id1, user_id2) kullanabilecekti, ancak tüm durumlar için bir index (user_id2, user_id1) kullanamayacaktı.

Bununla uğraştıktan sonra (Erwin çok nazikçe çalıştığı yerde bize bir kurulum gösterdikten sonra), bu durumun en iyi duruma getiricinin izleyen sütunları kullanmasını sağladığını henüz belirlemediğim halde, ikinci sütunun veri dağılımına bağlı olduğu görünüyor. NEREDE bir durum için.

Ayrıca (bazen) Oracle 11, dizin tanımının başında olmayan sütunları da kullanabilir.

re 2) Evet, bir dizin yaratacaktır

Kılavuzdan alıntı

Birincil anahtar eklemek, birincil anahtarda kullanılan sütun veya sütun grubu üzerinde otomatik olarak benzersiz bir btree dizini oluşturur.

2a yeniden) Primary Key (user_id1,user_id2)kendiniz öğrenebilirsiniz (user_id1, user_id2) (bir dizin yaratacak çok basitçe böyle bir birincil anahtar oluşturarak kolayca)

Kılavuzdaki indeksler ile ilgili bölümü okumanızı şiddetle tavsiye ediyorum , yukarıdaki tüm sorulara cevap veriyor.

Ek olarak, hangi endeks oluşturulur? Depesz tarafından dizin sütunları ve diğer dizin ile ilgili konulardaki sırayı açıklayan iyi bir iş çıkarır.


11

Reklam 1)
PostgreSQL'de @a_horse_with_no_name in tarif ettiği gibi sınırlamalar vardır . 8.0 sürümüne kadar multicolumn indexleri yalnızca baştaki sütunlardaki sorgular için kullanılabilir. Bu, 8.1 sürümünde geliştirildi. Postgres 10 şu anki elle (güncellendi) açıklıyor:

Çok sütunlu bir B ağacı dizini, dizinin sütunlarının herhangi bir alt kümesini içeren sorgu koşullarıyla kullanılabilir, ancak dizin (en soldaki) sütunlarda kısıtlamalar olduğunda en verimli olur. Kesin kural, önde gelen sütunlarda eşitlik kısıtlarının yanı sıra, ilk sütunda eşitlik kısıtı olmayan tüm eşitsizlik kısıtlarının taranan dizinin bir bölümünü sınırlamak için kullanılmasıdır. Bu sütunların sağındaki sütunlar üzerindeki kısıtlamalar dizinde kontrol edilir, böylece masaya yapılan ziyaretleri uygun şekilde kaydeder, ancak dizinin taranması gereken kısmını azaltmaz. Örneğin, bir dizin (a, b, c)ve bir sorgu koşulu verildiğinde, dizinin WHERE a = 5 AND b >= 42 AND c < 77ilk girişten a= 5 veb= 42 ile a= 5 olan son girişe kadar c> = 77 olan indeks girişleri atlanır, ancak yine de taranmaları gerekir. Bu indeks prensipte kısıtlı olan bve / veya ckısıtlı olmayan sorgular için kullanılabilir a- ancak tüm endeks taranması gerekirdi, bu yüzden çoğu durumda planlamacı endeksi kullanarak sıralı tablo taramasını tercih ederdi.

Vurgu madeni. Bunu deneyimden onaylayabilirim.
Ayrıca daha sonra cevabımı buraya eklediğim test vakasına bakın .


11

Bu Jack'in cevabına cevap , yorum yapmaz.

Orada PostgreSQL hiçbir kapsayan endeksler sürümü 9.2 önce. MVCC modeli nedeniyle, sonuç kümesindeki her bağ görünürlüğü kontrol etmek için ziyaret edilmelidir. Oracle'ı düşünüyor olabilirsiniz.

PostgreSQL geliştiricileri "yalnızca dizin taramaları" hakkında konuşurlar . Aslında, özellik Postgres 9.2 ile birlikte yayımlandı. Mesajını oku .
Depesz çok bilgilendirici bir Blog yazısı yazdı .

Gerçek örtü endeksleri (güncelleme), INCLUDEPostgres 11 maddesi ile birlikte verilmiştir.

Bu da biraz kapalı:

Bir dizinin 'tam taramasının', dizinde görünmeyen fazladan sütunlar nedeniyle, dizinlenmiş tablonun 'tam taramasından' daha hızlı olduğu gerçeğine dayanır.

Diğer cevabımdaki yorumlarda da belirtildiği gibi, iki tamsayı içeren bir tablo ve başka hiçbir şey yapmadan testler yaptım. Dizin, tabloyla aynı sütunları tutar. Bir btree endeksinin büyüklüğü tablonun 2 / 3'ü kadardır. Faktör 3'ün hızlanmasını açıklamak için yeterli değil. Ayarlarınıza göre iki sütuna basitleştirilmiş ve 100000 satırla daha fazla test yaptım. PostgreSQL 9.0 kurulumumda sonuçlar tutarlıydı.

Tablonun ek sütunları varsa , indeksli hız daha önemli hale gelir, ancak kesinlikle buradaki tek faktör bu değildir .

Ana noktaları özetlemek için:

  • Çok sütunlu dizinler, öncü olmayan sütunlardaki sorgular ile kullanılabilir, ancak hızlandırma yalnızca seçmeli ölçütler için faktör 3 civarındadır (sonuçtaki satırların küçük yüzdesi). Daha büyük perdeler için daha yüksek, sonuç kümesinde tablonun daha büyük bölümleri için daha düşük.

  • Performans önemliyse, bu sütunlarda ek bir dizin oluşturun.

  • İlgili tüm sütunlar bir dizine dahil edilmişse (dizini kapsayan) ve ilgili tüm satırlar (blok başına) tüm işlemlerde görülebiliyorsa , sayfa 9.2'de veya sonrasında "yalnızca dizin taraması" alabilirsiniz .


7
  1. Bunlar eşdeğer mi? Öyleyse neden olmasın?

    Dizin (user_id1, user_id2) ve Dizin (user_id2, user_id1)

Bunlar eşdeğer değildir ve genel olarak konuşan endeks (bar, baz) form sorguları için etkili olmayacaktır. select * from foo where baz=?

Erwin , bu tür endekslerin gerçekten bir sorguyu hızlandırabileceğini göstermiştir , ancak bu etki sınırlıdır ve genellikle bir dizinin bir aramayı iyileştirmesini beklediğiniz sırayla aynı değildir - bir dizinin 'tam taramasının' sık sık olduğu gerçeğine dayanır. Dizinde görünmeyen fazladan sütunlar nedeniyle dizine alınmış tablonun 'tam taramasından' daha hızlıdır.

Özet: dizinler, baştaki olmayan sütunlarda bile sorgulara yardımcı olabilir, ancak iki ikincil ve göreceli olarak küçük yollardan birinde ve dramatik bir şekilde değil, normalde btree yapısı nedeniyle yardımcı olmasını beklersiniz.

nb endeksin yardımcı olabileceği iki yol, endeksin tam olarak taranması tablonun tam taramasından önemli ölçüde daha ucuzsa: 1. Tablo aramaları ucuzdur (çünkü bunlardan az sayıda vardır veya kümelenmiştir) veya 2. Endeks kaplıyor, bu yüzden bütün ayylerde masaya bakma yok , Erwins'in burada yaptığı yorumları gör

test ortamı:

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;

sorgu 1 (dizin yok, 74 arabelleğe çarpma ):

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

sorgu 2 (indeks ile - optimizer endeksi yok sayar - tekrar 74 arabelleğe basılır):

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

Query 2 (index ile - ve optimize ediciyi kullanması için kandırıyoruz):

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

Bu nedenle, dizin üzerinden erişim bu durumda iki kez daha hızlıdır, bu durumda 30 arabellek vurulur - bu, dizin oluşturma açısından 'biraz daha hızlıdır'! tablodaki verilerin

Buna karşılık, önde gelen sütundaki sorgular, dizinin btree yapısını kullanır - bu durumda 2 arabelleğe çarpma :

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.