Birincil anahtardaki dizin basit birleştirmede kullanılmaz


16

Aşağıdaki tablo ve dizin tanımları var:

CREATE TABLE munkalap (
    munkalap_id serial PRIMARY KEY,
    ...
);

CREATE TABLE munkalap_lepes (
    munkalap_lepes_id serial PRIMARY KEY,
    munkalap_id integer REFERENCES munkalap (munkalap_id),
    ...
);

CREATE INDEX idx_munkalap_lepes_munkalap_id ON munkalap_lepes (munkalap_id);

Neden munkalap_id dizinlerinin hiçbiri aşağıdaki sorguda kullanılmıyor?

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id);

QUERY PLAN
Hash Join  (cost=119.17..2050.88 rows=38046 width=214) (actual time=0.824..18.011 rows=38046 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.005..4.574 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=3252 width=4) (actual time=0.810..0.810 rows=3253 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 115kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=3252 width=4) (actual time=0.003..0.398 rows=3253 loops=1)
Total runtime: 19.786 ms

Bir filtre eklesem bile aynı şey:

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id) WHERE NOT lezarva;

QUERY PLAN
Hash Join  (cost=79.60..1545.79 rows=1006 width=214) (actual time=0.616..10.824 rows=964 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.007..5.061 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=86 width=4) (actual time=0.587..0.587 rows=87 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 4kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=86 width=4) (actual time=0.014..0.560 rows=87 loops=1)
              Filter: (NOT lezarva)
Total runtime: 10.911 ms

Yanıtlar:


22

Birçok kişi "sıralı taramaların kötü olduğu" konusunda rehberlik duymuştur ve bunları planlarından çıkarmaya çalışmaktadır, ancak o kadar basit değildir. Bir sorgu tablodaki her satırı kapsayacaksa sıralı tarama bu satırları almanın en hızlı yoludur. Bu nedenle, orijinal birleştirme sorgunuz seq taraması kullandı, çünkü her iki tabloda da tüm satırlar gerekliydi.

Bir sorgu planlarken, Postgres'in planlayıcısı çeşitli işlemlerin maliyetlerini (hesaplama, sıralı ve rastgele IO) farklı olası şemalar altında tahmin eder ve tahmin ettiği planı en düşük maliyete sahip olarak seçer. Dönen depolamadan (diskler) IO yaparken, rastgele IO genellikle sıralı IO'dan önemli ölçüde daha yavaştır, random_page_cost ve seq_page_cost için varsayılan pg yapılandırması maliyette 4: 1 fark olduğunu tahmin eder.

Bu düşünceler, bir tabloyu sırayla tarayan bir dizin veya bir dizin kullanan bir birleştirme veya filtre yöntemi düşünülürken devreye girer. Bir indeks kullanırken, plan indeks üzerinden hızlı bir şekilde bir satır bulabilir, ardından satır verilerini çözmek için rastgele bir blok okumayı hesaba katmak zorunda kalabilir. Filtreleme yüklemesi ekleyen ikinci sorgunuzda WHERE NOT lezarva, bunun AÇIKLAMA ANALİZİ sonuçlarında planlama tahminlerini nasıl etkilediğini görebilirsiniz. Planlayıcı, birleşmeden kaynaklanan 1006 satırı tahmin eder (964'in gerçek sonuç kümesiyle oldukça yakından eşleşir). Daha büyük munkalap_lepes tablosunun yaklaşık 38K satır içerdiği göz önüne alındığında, planlayıcı birleşimin tablodaki satırların yaklaşık 1006/38046 veya 1 / 38'ine erişmesi gerektiğini görür. Ayrıca, ortalama satır genişliğinin 214 bayt olduğunu ve bir blok 8K olduğunu biliyor, bu nedenle yaklaşık 38 satır / blok var.

Bu istatistiklerle, planlayıcı birleştirme işleminin tablonun veri bloklarının tümünü veya çoğunu okumak zorunda kalacağını düşünür. Dizin aramaları da ücretsiz olmadığından ve bir filtre koşulunu değerlendiren bir bloğu taramak için hesaplama IO'ya göre çok ucuz olduğundan, planlayıcı tabloyu sırayla taramayı ve sıra taramasını hesaplarken dizin ek yükünü ve rastgele okumaları önlemeyi seçti daha hızlı olacak.

Gerçek dünyada, veriler genellikle OS sayfa önbelleği aracılığıyla bellekte bulunur ve bu nedenle okunan her blok IO gerektirmez. Bir önbelleğin belirli bir sorgu için ne kadar etkili olacağını tahmin etmek zor olabilir, ancak Pg planlayıcısı bazı basit sezgisel tarama kullanır. Yapılandırma değer effective_cache_size fiili IO maliyet tahakkuk etme olasılığına planlayıcıları tahminleri bilgilendirir. Daha büyük bir değer, rastgele G / Ç'ye daha düşük bir maliyet tahmin etmesine neden olur ve bu nedenle onu sıralı bir tarama üzerinden indeks güdümlü bir yönteme doğru yönlendirebilir.


Teşekkürler, bu şimdiye kadar okuduğum en iyi (ve en özlü) açıklama. Birkaç kilit nokta açıklığa kavuşturuldu.
dezso

1
Mükemmel açıklama. Yine de, satırların / veri sayfalarının hesaplanması biraz kapalıdır. Sayfa üstbilgisini (24 bayt) + her satır öğesi işaretçisi için 4 bayt + satır üstbilgisi HeapTupleHeader(satır başına 23 bayt) + MAXALIGN'a göre NULL bitmask + hizalamasını hesaba katmanız gerekir. Son olarak, sütunların veri türlerine ve sıralarına bağlı olarak veri hizalaması nedeniyle bilinmeyen bir dolgu miktarı. Sonuç olarak, bu durumda 8 kb'lik bir sayfada 33'ten fazla satır yoktur. (TOAST dikkate alınmaz.)
Erwin Brandstetter

1
@ErwinBrandstetter Daha titiz satır boyutu hesaplamaları doldurduğunuz için teşekkür ederiz. Her zaman açıklamak için satır genişliği tahmin çıktı üstbilgi ve NULL-bitmask gibi satır başına dikkate içerir, ancak sayfa düzeyinde ek yükü olacağını varsaymıştı.
dbenhur

1
@ dbenhur: EXPLAIN ANALYZE SELECT foo from barDoğrulamak için basit bir kukla tabloyla hızlıca çalıştırabilirsiniz . Ayrıca, disk üzerinde gerçek alan, yalnızca bazı satırlar alındığında dikkate alınması zor olan veri hizalamasına bağlıdır. Satır genişliği EXPLAIN, alınan sütun kümesi için temel alan gereksinimini temsil eder.
Erwin Brandstetter

5

Her iki tablodan da tüm satırları alıyorsunuz, bu nedenle dizin taraması kullanmanın gerçek bir yararı yok. Dizin taraması yalnızca bir tablodan yalnızca birkaç satır seçerseniz anlamlı olur (genellikle% 10 -% 15'ten az)


Evet, haklısın :) Daha spesifik bir durumla durumu açıklığa kavuşturmaya çalıştım, son sorguyu görün.
dezso

@dezso: Aynı şey. Bir dizininiz varsa (lezarva, munkalap_id)ve yeterince seçiciyseniz, o zaman kullanılabilir. NOTDaha az muhtemel hale getirir.
ypercubeᵀᴹ

Öneriye dayanarak kısmi bir dizin ekledim ve kullanılıyor, bu yüzden sorunun yarısı çözüldü. Ancak orijinal 3252 ile karşılaştırıldığında sadece 87 değere katılmak istediğim için yabancı anahtarın endeksinin işe yaramaz olmasını beklemem.
dezso

1
@dezso Satırlar 214 bayt genişliğinde, böylece 8K veri bloğu başına 40 satırın biraz altında olacaksınız. Endeksin seçiciliği de yaklaşık 1/40 (1006/38046) 'dır. Bu nedenle, Pg, tüm blokları sırayla okumanın indeksi kullanırken rasgele aynı sayıda bloğun olası okunmasından daha ucuz olduğunu rakamlar. Bu tahmini işlemler etkili_cache_size ve random_page_cost yapılandırma değerlerinden etkilenebilir.
dbenhur

@dbenhur: Yorumunuzu uygun bir cevap verebilir misiniz?
dezso
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.