Alt sorgu eklendiğinde PostgreSQL sorgusu çok yavaş


10

1.5M satır içeren bir tabloda nispeten basit bir sorgu var:

SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;

EXPLAIN ANALYZE çıktı:

Limit  (cost=8.84..12.86 rows=1 width=8) (actual time=0.985..0.986 rows=1 loops=1)
  ->  Bitmap Heap Scan on publication  (cost=8.84..12.86 rows=1 width=8) (actual time=0.984..0.985 rows=1 loops=1)
        Recheck Cond: ((mtid = 9762715) OR (last_modifier = 21321))
        ->  BitmapOr  (cost=8.84..8.84 rows=1 width=0) (actual time=0.971..0.971 rows=0 loops=1)
              ->  Bitmap Index Scan on publication_pkey  (cost=0.00..4.42 rows=1 width=0) (actual time=0.295..0.295 rows=1 loops=1)
                    Index Cond: (mtid = 9762715)
              ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..4.42 rows=1 width=0) (actual time=0.674..0.674 rows=0 loops=1)
                    Index Cond: (last_modifier = 21321)
Total runtime: 1.027 ms

Şimdiye kadar iyi, hızlı ve mevcut endeksleri kullanır.
Şimdi, bir sorguyu biraz değiştirirsem sonuç şu olur:

SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;

EXPLAIN ANALYZEÇıktısı:

Limit  (cost=0.01..2347.74 rows=5000 width=8) (actual time=2735.891..2841.398 rows=1 loops=1)
  ->  Seq Scan on publication  (cost=0.01..349652.84 rows=744661 width=8) (actual time=2735.888..2841.393 rows=1 loops=1)
        Filter: ((hashed SubPlan 1) OR (last_modifier = 21321))
        SubPlan 1
          ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
Total runtime: 2841.442 ms

Çok hızlı değil ve seq taraması kullanıyor ...

Tabii ki, uygulama tarafından çalıştırılan orijinal sorgu biraz daha karmaşık ve hatta daha yavaş ve elbette hazırda bekletilen oluşturulan orijinal değil (SELECT 9762715), ama bunun için bile yavaşlık var (SELECT 9762715)! Sorgu hazırda bekletme tarafından oluşturulur, bu nedenle bunları değiştirmek oldukça zordur ve bazı özellikler mevcut değildir (örneğin UNION, hızlı olan) kullanılamaz).

Sorular

  1. İndeks ikinci durumda neden kullanılamıyor? Nasıl kullanılabilirler?
  2. Sorgu performansını başka bir şekilde artırabilir miyim?

Ek düşünceler

İlk durumu el ile bir SELECT yaparak ve ardından ortaya çıkan listeyi sorguya koyarak kullanabiliriz. IN () listesindeki 5000 rakamla bile ikinci çözümden dört kat daha hızlıdır. Ancak, sadece YANLIŞ görünüyor (ayrıca, 100 kat daha hızlı olabilir :)). Neden sorgu planlayıcısı bu iki sorgu için tamamen farklı bir yöntem kullanıyor tamamen anlaşılmaz, bu yüzden bu soruna daha güzel bir çözüm bulmak istiyorum.


Bir şekilde kodunuzu yeniden yazabilir, böylece hazırda bekletme bir JOINyerine oluşturur IN ()mu? Ayrıca, publicationson zamanlarda analiz edildi mi?
dezso

Evet, hem VAKUM ANALİZİ hem de VAKUM DOLU yaptım. Performansta bir değişiklik olmadı. İkincisi, AFAIR bunu denedik ve sorgu performansını önemli ölçüde etkilemedi.
P.Péter

1
Hazırda Bekletme düzgün bir sorgu oluşturamazsa, neden yalnızca ham SQL kullanmıyorsunuz? Bu, İngilizce'de nasıl ifade edeceğinizi daha iyi biliyorken Google çevirisinde ısrar etmek gibi bir şey. Sorunuza gelince: bu gerçekten gizli gizli sorguya bağlıdır (SELECT 9762715).
Erwin Brandstetter

Aşağıda belirtildiği gibi, iç sorgu bile yavaş olduğunu (SELECT 9762715) . Hazırda bekletme sorusuna: yapılabilir, ancak anında çevrilen kullanıcı tanımlı hazırda bekletme ölçütleri sorgularımız olduğu için ciddi kod yeniden yazma gerektirir. Aslında, birçok olası yan etkisi olan büyük bir girişim olan kış uykusunu değiştireceğiz.
P.Péter

Yanıtlar:


6

Sorunun özü burada belirginleşiyor:

Yayında Seq Scan (maliyet = 0.01..349652.84 satır = 744661 genişlik = 8) (gerçek zaman = 2735.888..2841.393 satır = 1 döngü = 1)

Postgres , 744661 satır döndüreceğini tahmin ederken, aslında tek bir satır olduğu ortaya çıkıyor. Postgres, sorgudan ne bekleyeceğini daha iyi bilmiyorsa, daha iyi planlayamaz. Gerçek sorguyu gizli olarak görmeliyiz (SELECT 9762715)- ve muhtemelen tablo tanımını, kısıtlamaları, kardinalitelerini ve veri dağıtımını da bilmeliyiz. Açıkçası, Postgres kaç satırın geri döndürüleceğini tahmin edemez. Ne bağlı sorguyu yeniden yazmak için yollar olabilir olduğunu .

Eğer varsa bilmek alt sorgu birden asla geri dönemez o nsıralar, sadece kullanarak Postgres söyleyebilir:

SELECT mtid
FROM   publication
WHERE  mtid IN (SELECT ... LIMIT n) --  OR last_modifier=21321
LIMIT  5000;

Eğer n küçük yeterlidir Postgrees (bitmap) endeks taramaları geçer. Ancak , bu sadece basit dava için geçerlidir. Bir ORkoşul eklerken çalışmayı durdurur : sorgu planlayıcı şu anda bununla baş edemiyor.

IN (SELECT ...)Başlamak için nadiren kullanırım . Genellikle aynısını, genellikle EXISTSyarı birleştirme ile uygulamak için daha iyi bir yol vardır . Bazen bir ( LEFT) JOIN( LATERAL) ile ...

Bariz geçici çözüm kullanmak olacaktır UNION, ancak bunu dışladınız. Gerçek alt sorguyu ve diğer ilgili ayrıntıları bilmeden daha fazlasını söyleyemem.


2
Orada hiçbir sorgu arkasında gizli (SELECT 9762715) ! Yukarıda gördüğünüz tam sorguyu çalıştırırsam. Tabii ki, orijinal hazırda bekleme sorgusu biraz daha karmaşık, ama ben (sanırım ben) sorgu planlayıcısı sapmaya nereye saptamayı başardı, bu yüzden sorgunun o kısmını sundu. Bununla birlikte, yukarıdaki açıklamalar ve sorgular ctrl-cv kelimesidir.
P.Péter

İkinci bölüme gelince, iç sınır işe yaramaz: EXPLAIN ANALYZE SELECT mtid FROM publication WHERE mtid IN (SELECT 9762715 LIMIT 1) OR last_modifier=21321 LIMIT 5000;ayrıca sıralı bir tarama yapar ve yaklaşık 3 saniye çalışır ...
P.Péter

@ P.Péter: Postgres 9.4'teki gerçek bir alt sorgu ile yerel sınavımda benim için çalışıyor. Gösterdiğiniz şey gerçek sorgunuzsa, çözümünüz zaten vardır: Sorunuzdaki ilk sorguyu alt sorgu yerine sabitle kullanın.
Erwin Brandstetter

Eh, ben de yeni bir test masada bir alt sorgu çalıştı: CREATE TABLE test (mtid bigint NOT NULL, last_modifier bigint, CONSTRAINT test_property_pkey PRIMARY KEY (mtid)); CREATE INDEX test_last_modifier_btree ON test USING btree (last_modifier); INSERT INTO test (mtid, last_modifier) SELECT mtid, last_modifier FROM publication;. Ve etkisi hala aynı sorgular için oradaydı test: herhangi bir alt sorgu bir seq taramasıyla sonuçlandı ... Hem 9.1 hem de 9.4'ü denedim. Etkisi aynı.
P.Péter

1
@ P.Péter: Testi tekrar yaptım ve ORkoşul olmadan test ettiğimi fark ettim . İle hile LIMITsadece daha basit dava için çalışır.
Erwin Brandstetter

6

Meslektaşım, basit bir yeniden yazmaya ihtiyaç duyması için sorguyu değiştirmenin bir yolunu buldu ve yapması gerekeni yapıyor, yani bir adımda alt seçimi yapmak ve daha sonra sonuç üzerinde daha fazla işlem yapmak:

SELECT mtid FROM publication 
WHERE 
  mtid = ANY( (SELECT ARRAY(SELECT 9762715))::bigint[] )
  OR last_modifier=21321
LIMIT 5000;

Açıklamak şimdi analiz:

 Limit  (cost=92.58..9442.38 rows=2478 width=8) (actual time=0.071..0.074 rows=1 loops=1)
   InitPlan 2 (returns $1)
     ->  Result  (cost=0.01..0.02 rows=1 width=0) (actual time=0.010..0.011 rows=1 loops=1)
           InitPlan 1 (returns $0)
             ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.002 rows=1 loops=1)
   ->  Bitmap Heap Scan on publication  (cost=92.56..9442.36 rows=2478 width=8) (actual time=0.069..0.070 rows=1 loops=1)
         Recheck Cond: ((mtid = ANY (($1)::bigint[])) OR (last_modifier = 21321))
         Heap Blocks: exact=1
         ->  BitmapOr  (cost=92.56..92.56 rows=2478 width=0) (actual time=0.060..0.060 rows=0 loops=1)
               ->  Bitmap Index Scan on publication_pkey  (cost=0.00..44.38 rows=10 width=0) (actual time=0.046..0.046 rows=1 loops=1)
                     Index Cond: (mtid = ANY (($1)::bigint[]))
               ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..46.94 rows=2468 width=0) (actual time=0.011..0.011 rows=0 loops=1)
                     Index Cond: (last_modifier = 21321)
 Planning time: 0.704 ms
 Execution time: 0.153 ms

Görünüşe göre, bu şekilde tüm alt seçimleri bulan ve yeniden yazan basit bir ayrıştırıcı oluşturabilir ve yerel sorguyu değiştirmek için bir hazırda bekletme kancasına ekleyebiliriz.


Bu eğlenceli geliyor. Sorunuzdaki SELECTilk sorgunuzda olduğu gibi tüm s'leri kaldırmak daha kolay değil mi?
dezso

Tabii ki, iki aşamalı bir yaklaşım yapabilirim: SELECTayrı ayrı yapın ve sonra dış seçimi statik bir listeden sonra yapın IN. Ancak, bu çok daha yavaştır (alt sorgu birkaç sonuçtan daha fazla ise 5-10 kez), ekstra ağ gidiş-dönüşleriniz olduğu için postgres çok sayıda sonucu biçimlendirirsiniz ve daha sonra java bu sonuçları ayrıştırır (ve sonra aynı şeyi tekrar geriye doğru). Yukarıdaki çözüm, semantik olarak aynı şeyi yaparken, süreci postgres içinde bırakır. Sonuçta, şu anda bu, bizim durumumuzdaki en küçük değişiklikle en hızlı yol gibi görünüyor.
P.Péter

Ah, anlıyorum. Bilmediğim, bir kerede birçok kimlik alabilmeniz.
dezso

1

İkinci bir sorunun cevabı: Evet, alt sorunuza ORDER BY ekleyebilirsiniz, bu da olumlu etki yaratacaktır. Ancak performansta "EXISTS (alt sorgu)" çözümü ile benzerdir. İki sıra ile sonuçlanan alt sorgularda bile önemli bir fark vardır.

SELECT mtid FROM publication
WHERE mtid IN (SELECT #column# ORDER BY #column#) OR last_modifier=21321
LIMIT 5000;
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.