GROUP BY ve ORDER BY ile büyük tabloda yavaş sorgu


14

Ben aşağıdaki gibi 7.2 milyon tuples ile bir tablo var:

                               table public.methods
 column |          type         |                      attributes
--------+-----------------------+----------------------------------------------------
 id     | integer               | not null DEFAULT nextval('methodkey'::regclass)
 hash   | character varying(32) | not null
 string | character varying     | not null
 method | character varying     | not null
 file   | character varying     | not null
 type   | character varying     | not null
Indexes:
    "methods_pkey" PRIMARY KEY, btree (id)
    "methodhash" btree (hash)

Şimdi bazı değerleri seçmek istiyorum ama sorgu inanılmaz derecede yavaş:

db=# explain 
    select hash, string, count(method) 
    from methods 
    where hash not in 
          (select hash from nostring) 
    group by hash, string 
    order by count(method) desc;
                                            QUERY PLAN
----------------------------------------------------------------------------------------
 Sort  (cost=160245190041.10..160245190962.07 rows=368391 width=182)
   Sort Key: (count(methods.method))
   ->  GroupAggregate  (cost=160245017241.77..160245057764.73 rows=368391 width=182)
       ->  Sort  (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
             Sort Key: methods.hash, methods.string
             ->  Seq Scan on methods  (cost=0.00..160243305942.27 rows=3683905 width=182)
                   Filter: (NOT (SubPlan 1))
                   SubPlan 1
                   ->  Materialize  (cost=0.00..41071.54 rows=970636 width=33)
                     ->  Seq Scan on nostring  (cost=0.00..28634.36 rows=970636 width=33)

hashSütun MD5 hash olduğunu stringve bir dizin var. Bu yüzden benim sorunum tüm tablo karma tarafından değil, id tarafından sıralanmış olduğunu, bu yüzden önce sıralamak ve sonra gruplandırmak biraz zaman alır olmasıdır?

Tabloda nostringyalnızca sahip olmak istemediğim karmaların listesi var. Ama tüm değerleri olması için her iki tabloya ihtiyacım var. Bu yüzden bunları silmek için bir seçenek değil.

ek bilgi: sütunların hiçbiri null (tablo tanımındaki sabit) olabilir ve ben postgresql 9.2 kullanıyorum.


1
Her zaman kullandığınız PostgreSQL sürümünü sağlayın . NULLSütundaki değerlerin yüzdesi nedir method? Üzerinde kopyalar var stringmı?
Erwin Brandstetter

Yanıtlar:


18

LEFT JOINİçinde @ Dezso cevabı iyi olmalı. Bununla birlikte, bir dizin (kendi başına) neredeyse hiç kullanışlı olmayacaktır, çünkü sorgu yine de tüm tabloyu okumalıdır - istisna Postgres 9.2+ ve uygun koşullarda yalnızca dizin taramalarıdır, aşağıya bakınız.

SELECT m.hash, m.string, count(m.method) AS method_ct
FROM   methods m
LEFT   JOIN nostring n USING (hash)
WHERE  n.hash IS NULL
GROUP  BY m.hash, m.string 
ORDER  BY count(m.method) DESC;

EXPLAIN ANALYZESorgu üzerinde çalıştırın . Nakit efektlerini ve gürültüyü dışlamak için birkaç kez. En iyi sonuçları karşılaştırın.

Sorgunuzla eşleşen çok sütunlu bir dizin oluşturun:

CREATE INDEX methods_cluster_idx ON methods (hash, string, method);

Bekle? Bir endeksin yardımcı olmayacağını söyledikten sonra? CLUSTERMasaya ihtiyacımız var :

CLUSTER methods USING methods_cluster_idx;
ANALYZE methods;

Tekrar çalıştır EXPLAIN ANALYZE. Daha hızlı? Olmalı.

CLUSTERtüm tabloyu kullanılan dizin sırasına göre yeniden yazmak için tek seferlik bir işlemdir. Aynı zamanda etkili bir a VACUUM FULL. Emin olmak istiyorsanız, VACUUM FULLneyle ilişkilendirilebileceğini görmek için tek başına bir ön test uygularsınız .

Tablonuz çok fazla yazma işlemi görürse, efekt zamanla azalır. CLUSTEREfekti geri yüklemek için mesai saatleri dışında programlayın . İnce ayar, tam kullanım durumunuza bağlıdır. Hakkında kılavuz CLUSTER.

CLUSTERoldukça kaba bir araçtır, masanın üzerinde özel bir kilit gerekir. Eğer bunu karşılayamıyorsanız, pg_repacközel kilit olmadan da aynı şeyi yapabilirsiniz. Daha sonraki cevapta daha fazlası:


Eğer yüzdesi NULLsütunundaki değerlere method, bir (~ 20 oranında, gerçek satır boyutuna bağlı olarak daha fazla), yüksek olduğu kısmi indeks yardımcı olacaktır:

CREATE INDEX methods_foo_idx ON methods (hash, string)
WHERE method IS NOT NULL;

(Daha sonraki güncellemeniz sütunlarınızın geçerli olduğunu gösterir NOT NULL, bu nedenle uygulanamaz.)

Eğer PostgreSQL çalıştıran 9.2 veya üstü (aynı @deszo yorumladı ) olmadan sunulan indeksleri yararlı olabilir CLUSTERplanlayıcısı kullanabileceği eğer endeks salt taramalar . Yalnızca uygun koşullar altında geçerlidir: VACUUMSorgudaki son ve tüm sütunların dizin tarafından kapsanması gerektiğinden , görünürlük haritasını etkileyecek yazma işlemleri yoktur . Temel olarak salt okunur tablolar bunu her zaman kullanabilirken, yoğun olarak yazılmış tablolar sınırlıdır. Postgres Wiki'de daha fazla ayrıntı.

Yukarıda belirtilen kısmi endeks bu durumda daha da yararlı olabilir.

Eğer , diğer taraftan, orada hiçbir NULL sütunundaki değerler method, sen gerektiğini
1.) o tanımlamak NOT NULLve
2.) kullanımını count(*)yerine count(method)biraz daha hızlı olduğunu ve yokluğunda aynısını yapar, NULLdeğerler.

Eğer sık sık bu sorguyu aramak zorunda ve tablo salt okunur bir oluşturmak MATERIALIZED VIEW.


Egzotik ince nokta: Tablonuz adlandırıldı nostring, ancak karma içeriyor gibi görünüyor. Dizeler yerine karmaları hariç tutarak, beklenenden daha fazla dizeyi hariç tutma şansınız vardır. Son derece düşük, ancak mümkün.


küme ile çok daha hızlı. hala sorgu için 5 dakika etrafında gerekir ama bu bütün gece çalıştırmak daha çok daha iyi: D
reox

@reox: v9.2'yi çalıştırdığınız için: Kümelemeden önce yalnızca dizinle test yaptınız mı? Bir fark görürseniz ilginç olurdu. (Kümelemeden sonra farkı yeniden oluşturamazsınız.) Ayrıca (ve bu ucuz olurdu), EXPLAIN şimdi bir dizin taraması veya tam bir tablo taraması gösteriyor mu?
Erwin Brandstetter

5

DBA.SE'ye Hoşgeldiniz!

Sorgunuzu şu şekilde yeniden silmeyi deneyebilirsiniz:

SELECT m.hash, string, count(method) 
FROM 
    methods m
    LEFT JOIN nostring n ON m.hash = n.hash
WHERE n.hash IS NULL
GROUP BY hash, string 
ORDER BY count(method) DESC;

veya başka bir olasılık:

SELECT m.hash, string, count(method) 
FROM 
    methods m
WHERE NOT EXISTS (SELECT hash FROM nostring WHERE hash = m.hash)
GROUP BY hash, string 
ORDER BY count(method) DESC;

NOT IN onunla bir indeks kullanmak zor olduğu için performans için tipik bir lavabo.

Bu, dizinlerle daha da geliştirilebilir. Üzerinde bir dizin nostring.hashyararlı görünüyor. Ama önce: şimdi ne alıyorsunuz? ( EXPLAIN ANALYZEMaliyetlerin kendileri operasyonların ne kadar zaman aldığını söylemediğinden çıktısını görmek daha iyi olacaktır .)


bir dizin nostring.hash allready üzerinde oluşturulur, ama ben postgres çok fazla tuples nedeniyle kullanmıyorum düşünüyorum ... i dizi taramasını devre dışı bırakmak explcit zaman, dizin kullanır. Eğer sol birleşimi kullanırsam 32 milyon maliyet alırım, bu yüzden daha iyi ... ama daha fazla optimize etmeye çalışıyorum ...
reox

3
Maliyet sadece planlayıcının yeterince iyi bir plan belirleyebilmesidir. Gerçek zamanlar genellikle onunla ilişkilidir, ancak zorunlu değildir. Eğer emin olmak istiyorsanız, kullanın EXPLAIN ANALYZE.
dezso

1

Karma bir md5 olduğundan, muhtemelen bir sayıya dönüştürmeyi deneyebilirsiniz: bir sayı olarak saklayabilir veya sadece değişmez bir fonksiyonda bu sayıyı hesaplayan fonksiyonel bir dizin oluşturabilirsiniz.

Diğer insanlar zaten bir md5 değerini metinden dizeye dönüştüren bir pl / pgsql işlevi oluşturdu. Örnek için /programming/9809381/hashing-a-string-to-a-numeric-value-in-postgressql adresine bakın.

Gerçekten dizin tararken dize karşılaştırma çok zaman harcamak inanıyoruz. Bu değeri bir sayı olarak depolamayı başarırsanız, gerçekten çok daha hızlı olmalıdır.


1
Bu dönüşümün işleri hızlandıracağından şüpheliyim. Buradaki tüm sorgular karşılaştırma için eşitlik kullanır. Sayısal temsiller hesaplamak ve sonra eşitliği kontrol etmek benim için büyük kazançlar vaat etmiyor.
dezso

2
Ben doğrusu uzay verimliliği için bir sayıdan bytea olarak md5 saklamak düşünüyorum: sqlfiddle.com/#!12/d41d8/252
Jack diyor topanswers.xyz deneyin

Ayrıca, dba.se'ye hoş geldiniz!
Jack diyor ki topanswers.xyz

@JackDouglas: İlginç bir yorum! 32 yerine md5 başına 16 bayt büyük tablolar için oldukça azdır.
Erwin Brandstetter

0

Bu konuya çok giriyorum ve basit bir 2 bölümlük hile keşfettim.

  1. Karma değerinde alt dize dizini oluşturun: (7 genellikle iyi bir uzunluktur)

    create index methods_idx_hash_substring ON methods(substring(hash,1,7))

  2. Aramalarınızın / birleştirmelerinizin bir alt dize eşlemesi içermesini sağlayın, bu nedenle sorgu planlayıcısının dizini kullanması önerilir:

    eski: WHERE hash = :kwarg

    yeni: WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))

Hamda da bir indeksiniz olmalıdır hash.

sonuç (genellikle), planlayıcının önce alt dize dizinine başvurması ve satırların çoğunu ayıklamasıdır. daha sonra 32 karakter karmasını karşılık gelen dizine (veya tabloya) eşleştirir. bu yaklaşım benim için 800 ms sorgu 4'e düştü.

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.