PostgreSQL neden daha pahalı birleştirme siparişi seçiyor?


13

Varsayılanları kullanarak PostgreSQL, artı

default_statistics_target=1000
random_page_cost=1.5

versiyon

PostgreSQL 10.4 on x86_64-pc-linux-musl, compiled by gcc (Alpine 6.4.0) 6.4.0, 64-bit

Süpürdüm ve analiz ettim. Sorgu çok basittir:

SELECT r.price
FROM account_payer ap
  JOIN account_contract ac ON ap.id = ac.account_payer_id
  JOIN account_schedule "as" ON ac.id = "as".account_contract_id
  JOIN schedule s ON "as".id = s.account_schedule_id
  JOIN rate r ON s.id = r.schedule_id
WHERE ap.account_id = 8

Her idsütun birincil anahtardır ve birleştirilen her şey bir yabancı anahtar ilişkisidir ve her yabancı anahtarın bir dizini vardır. Artı için bir dizin account_payer.account_id.

76 bin satır döndürmek 3,93 saniye sürer.

Merge Join  (cost=8.06..83114.08 rows=3458267 width=6) (actual time=0.228..3920.472 rows=75548 loops=1)
  Merge Cond: (s.account_schedule_id = "as".id)
  ->  Nested Loop  (cost=0.57..280520.54 rows=6602146 width=14) (actual time=0.163..3756.082 rows=448173 loops=1)
        ->  Index Scan using schedule_account_schedule_id_idx on schedule s  (cost=0.14..10.67 rows=441 width=16) (actual time=0.035..0.211 rows=89 loops=1)
        ->  Index Scan using rate_schedule_id_code_modifier_facility_idx on rate r  (cost=0.43..486.03 rows=15005 width=10) (actual time=0.025..39.903 rows=5036 loops=89)
              Index Cond: (schedule_id = s.id)
  ->  Materialize  (cost=0.43..49.46 rows=55 width=8) (actual time=0.060..12.984 rows=74697 loops=1)
        ->  Nested Loop  (cost=0.43..49.32 rows=55 width=8) (actual time=0.048..1.110 rows=66 loops=1)
              ->  Nested Loop  (cost=0.29..27.46 rows=105 width=16) (actual time=0.030..0.616 rows=105 loops=1)
                    ->  Index Scan using account_schedule_pkey on account_schedule "as"  (cost=0.14..6.22 rows=105 width=16) (actual time=0.014..0.098 rows=105 loops=1)
                    ->  Index Scan using account_contract_pkey on account_contract ac  (cost=0.14..0.20 rows=1 width=16) (actual time=0.003..0.003 rows=1 loops=105)
                          Index Cond: (id = "as".account_contract_id)
              ->  Index Scan using account_payer_pkey on account_payer ap  (cost=0.14..0.21 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=105)
                    Index Cond: (id = ac.account_payer_id)
                    Filter: (account_id = 8)
                    Rows Removed by Filter: 0
Planning time: 5.843 ms
Execution time: 3929.317 ms

Ben ayarlarsanız join_collapse_limit=1, bu 0.16s, 25x hıza sürer.

Nested Loop  (cost=6.32..147323.97 rows=3458267 width=6) (actual time=8.908..151.860 rows=75548 loops=1)
  ->  Nested Loop  (cost=5.89..390.23 rows=231 width=8) (actual time=8.730..11.655 rows=66 loops=1)
        Join Filter: ("as".id = s.account_schedule_id)
        Rows Removed by Join Filter: 29040
        ->  Index Scan using schedule_pkey on schedule s  (cost=0.27..17.65 rows=441 width=16) (actual time=0.014..0.314 rows=441 loops=1)
        ->  Materialize  (cost=5.62..8.88 rows=55 width=8) (actual time=0.001..0.011 rows=66 loops=441)
              ->  Hash Join  (cost=5.62..8.61 rows=55 width=8) (actual time=0.240..0.309 rows=66 loops=1)
                    Hash Cond: ("as".account_contract_id = ac.id)
                    ->  Seq Scan on account_schedule "as"  (cost=0.00..2.05 rows=105 width=16) (actual time=0.010..0.028 rows=105 loops=1)
                    ->  Hash  (cost=5.02..5.02 rows=48 width=8) (actual time=0.178..0.178 rows=61 loops=1)
                          Buckets: 1024  Batches: 1  Memory Usage: 11kB
                          ->  Hash Join  (cost=1.98..5.02 rows=48 width=8) (actual time=0.082..0.143 rows=61 loops=1)
                                Hash Cond: (ac.account_payer_id = ap.id)
                                ->  Seq Scan on account_contract ac  (cost=0.00..1.91 rows=91 width=16) (actual time=0.007..0.023 rows=91 loops=1)
                                ->  Hash  (cost=1.64..1.64 rows=27 width=8) (actual time=0.048..0.048 rows=27 loops=1)
                                      Buckets: 1024  Batches: 1  Memory Usage: 10kB
                                      ->  Seq Scan on account_payer ap  (cost=0.00..1.64 rows=27 width=8) (actual time=0.009..0.023 rows=27 loops=1)
                                            Filter: (account_id = 8)
                                            Rows Removed by Filter: 24
  ->  Index Scan using rate_schedule_id_code_modifier_facility_idx on rate r  (cost=0.43..486.03 rows=15005 width=10) (actual time=0.018..1.685 rows=1145 loops=66)
        Index Cond: (schedule_id = s.id)
Planning time: 4.692 ms
Execution time: 160.585 ms

Bu çıktılar benim için çok anlamlı değil. İlki, zamanlama ve oran endeksleri için iç içe döngü birleştirme için 280.500 (çok yüksek) bir maliyete sahiptir. PostgreSQL neden kasten önce bu çok pahalı katılımı seçiyor?

Yorumlar aracılığıyla istenen ek bilgiler

rate_schedule_id_code_modifier_facility_idxbir bileşik endeksi?

Bununla beraber, bir schedule_idbirinci kolon olan. Onu özel bir dizin haline getirdim ve sorgu planlayıcısı tarafından seçildi, ancak performansı etkilemez veya planı başka şekilde etkilemez.


Ayarları değiştirebilir default_statistics_targetve random_page_costvarsayılan değerlerine geri dönebilir misiniz? Daha da yükseltirseniz ne olur default_statistics_target? Bir DB Fiddle (dbfiddle.uk'ta) yapabilir ve sorunu orada yeniden oluşturmaya çalışabilir misiniz?
Colin 't Hart

3
Verilerinizde çarpık / garip bir şey olup olmadığını görmek için gerçek istatistikleri inceleyebilir misiniz? postgresql.org/docs/10/static/planner-stats.html
Colin 't Hart

Parametre için geçerli değer nedir work_mem? Değiştirmek farklı zamanlamalar verir mi?
eppesuig

Yanıtlar:


1

Görünüşe göre ya istatistikleriniz doğru değil (bunları yenilemek için vakum analizini çalıştırın) ya modelinizde ilişkili sütunlara sahipsiniz (ve bu yüzden create statisticsplanlayıcıyı bu gerçeği bilgilendirmek için gerçekleştirmeniz gerekecek ).

join_collapseParametresi ilk az veri getirir böyle bir işlem, böylece yeniden düzenlemek için planlayıcısı katılır sağlar. Ancak, performans için, planlayıcının bunu çok sayıda birleştirmeyle bir sorguda yapmasına izin veremeyiz. Varsayılan olarak, maks. 1 olarak ayarladığınızda, bu özelliği devre dışı bırakmanız yeterlidir.

Peki postgres bu sorgunun kaç satırı getireceğini nasıl öngörüyor? Satır sayısını tahmin etmek için istatistikleri kullanır.

Açıklama planlarınızda görebildiğimiz, birkaç yanlış satır sayısı tahmini olmasıdır (birinci değer tahmin, ikincisi gerçek).

Örneğin, burada:

Materialize  (cost=0.43..49.46 rows=55 width=8) (actual time=0.060..12.984 rows=74697 loops=1)

Planlamacı aslında 74697 olduğunda 55 satır aldığını tahmin etti.

Ne yapardım (ayakkabılarınızda olsaydım):

  • analyze istatistikleri yenilemek için ilgili beş tablo
  • Tekrar explain analyze
  • Tahmini satır sayıları ile gerçek satır sayıları arasındaki farka bakın
  • Tahmini satır numaraları doğruysa, plan değişmiş olabilir ve daha verimlidir. Her şey yolundaysa, otomatik vakum ayarlarınızı değiştirmeyi düşünebilirsiniz, böylece analiz (ve vakum) daha sık performans gösterir
  • Tahmin satır numaraları hala yanlışsa sen (üçüncü normal formda ihlali) .Arzu ile deklare düşünebilirsiniz Tablonuzdaki veri korelasyon var gibi görünüyor CREATE STATISTICS(dokümantasyon burada )

Satır tahminleri ve hesaplamaları hakkında daha fazla bilgiye ihtiyacınız varsa, Tomas Vondra'nın "İstatistik oluştur - Ne için?" ( burada kayar )

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.