PostgreSQL'de görünümler performans açısından zararlı mı?


45

Aşağıdakiler, db design hakkında bir kitaptan bir alıntıdır (Veri Tabanı Tasarımına Başlarken ISBN: 0-7645-7490-6):

Görünümleri kullanma tehlikesi, görünümü çok büyük bir tablonun çok küçük bir bölümünü okumayı bekleyen bir görünüme karşı filtrelemektir. Görünümdeki sorgu yürütmeyi tamamladıktan sonra görünüme karşı herhangi bir filtreleme uygulandığından, filtreleme görünüm içinde yapılmalıdır. Görünümler genellikle geliştirme işlemini hızlandırmak için kullanışlıdır, ancak uzun vadede veritabanı performansını tamamen öldürebilir.

Aşağıdakiler PostgreSQL 9.5 Documentation'dan bir alıntıdır:

Görüşlerin liberal bir şekilde kullanılması, iyi bir SQL veritabanı tasarımının kilit bir yönüdür. Görünümler, uygulamanız ilerledikçe değişebilecek olan tablolarınızın yapısının ayrıntılarını tutarlı arabirimlerin arkasına yerleştirmenize olanak tanır.

İki kaynak birbiriyle çelişiyor gibi görünmektedir (“manzaralı tasarım yapmayın” vs. “manzaralı tasarım yap”).

Ancak, PG'de görünümler kural sistemi kullanılarak gerçekleştirilir. Bu nedenle, muhtemelen (ve bu benim sorum budur), görünüme karşı herhangi bir filtreleme, görünümün içinde bir filtre olarak yeniden yazılır ve bu, alttaki tablolara karşı tek bir sorgu yürütmesine neden olur.

Yorumum doğru mu ve PG, WHERE yan tümcelerini görünümün içinde ve dışında birleştiriyor mu? Yoksa onları ayrı ayrı mı, birbiri ardına çalıştırıyor? Herhangi bir kısa, kendi kendine yeten, doğru (derlenebilir) örnekler?


Sorunun doğru olmadığını düşünüyorum çünkü her iki kaynak da aynı şey hakkında konuşmuyor. Birincisi bir görünümden sorgu ile ilgilidir ve SONRA bir filtre uygular: SELECT * FROM my_view WHERE my_column = 'blablabla';İkincisi ise veri modelinizi onu kullanan uygulamaya karşı şeffaf yapmak için görünümleri kullanmaktır. İlk kaynaklar, filtreyi WHERE my_column = 'blablabla'görünüm tanımının içine dahil etmenize işaret eder , çünkü bu daha iyi bir uygulama planı ile sonuçlanır.
EAmez

Yanıtlar:


49

Kitap yanlış.

Bir görünümden seçilmesi olduğu tam olarak hızlı veya yavaş altında yatan SQL deyimi çalışan olarak - kolayca kullanarak kontrol edebilirsinizexplain analyze .

Postgres optimizeri (ve birçok diğer modern DBMS'ler için optimize edici), görünümdeki tahminleri gerçek görünüm ifadesine indirgeyebilecektir - bunun basit bir ifade olması şartıyla (yine, bu doğrulanabilir. explain analyze ).

Performansla ilgili "kötü şöhret" - bence - görünümleri aşırı kullanıp görünümleri kullanan görünümleri oluşturmaya başladığınızda. Çoğu zaman bu, bazı ara tablolara ihtiyaç duyulmadığı için, görünümler olmadan el yapımı olan bir ifadeyle karşılaştırıldığında çok fazla şey yapan ifadelerle sonuçlanır. Neredeyse tüm durumlarda, optimizer bu gereksiz tabloları / birleştirmeleri kaldırmak veya birden çok görünüm düzeyindeki öngörüleri aşağı çekmek için yeterince akıllı değildir (bu, diğer DBMS'ler için de geçerlidir).


3
Önerilen karşı cevaplardan bazıları göz önüne alındığında, basit bir ifadenin ne olduğu hakkında biraz bilgi vermek isteyebilirsiniz .
RDFozz

İfadenin nasıl kullanılacağını açıklayabilir misiniz explain analyze?
Dustin Michels

@DustinMichels: el kitabına bir göz atın: postgresql.org/docs/current/using-explain.html
a_horse_with_no_name

19

Size bir vermek gerekirse örnek ne @a_horse açıkladı :

Postgres , standartlaştırılmış biçimde DB nesneleri hakkında bilgi sağlayan (bazen karmaşık) görünümlerden oluşan bilgi şemasını uygular . Bu kullanışlı ve güvenilirdir - ve Postgres katalog tablolarına doğrudan erişmekten çok daha pahalı olabilir.

Bir tablonun tüm görünür sütunlarını
bilgi şemasından almak için çok basit bir örnek :

SELECT column_name
FROM   information_schema.columns
WHERE  table_name = 'big'
AND    table_schema = 'public';

... sistem kataloğundan:

SELECT attname
FROM   pg_catalog.pg_attribute
WHERE  attrelid = 'public.big'::regclass
AND    attnum > 0
AND    NOT attisdropped;

Sorgu planlarını ve yürütme süresini her ikisiyle de karşılaştırın EXPLAIN ANALYZE.

  • İlk sorgu information_schema.columns, bunun için ihtiyacımız olmayan birden fazla tabloya katılan görünüme dayanıyor .

  • İkinci sorgu sadece bir tabloyu tarar pg_catalog.pg_attribute, bu yüzden çok daha hızlı. (Ancak ilk sorgu hala ortak DB'lerde sadece birkaç ms'ye ihtiyaç duyar.)

Detaylar:


7

DÜZENLE:

Özür dilerim, kabul edilen cevabın her zaman doğru olmadığı iddiamı geri çekmem gerekiyor - görüşün her zaman bir alt sorgu olarak yazılanla aynı olduğunu söylüyor. Sanırım bu tartışılmaz ve sanırım şu anda davamda neler olup bittiğini biliyorum.

Şimdi asıl sorunun daha iyi bir cevabı olduğunu düşünüyorum.

Asıl soru, görüşlerin kullanılmasının rehberlik etmesi gerekip gerekmediğidir (örneğin, SQL’in iki kez veya daha fazla sürdürülmesi gerekebilecek rutinlerde tekrarlanması yerine).

Cevabım, "sorgunuz pencere işlevlerini veya optimizasyoncunun alt sorgu olduğunda farklı şekilde ele almasına neden olan herhangi bir şey kullanmıyorsa, alt sorguyu oluşturma eylemi (görünüm olarak temsil edilip edilmese de) performansı düşürebilir. Çalışma zamanında parametrelerle filtreleme yapıyorsanız.

Pencere fonksiyonumun karmaşıklığı gereksiz. Bunun için açıklama planı:

SELECT DISTINCT ts.train_service_key,
            pc.assembly_key,
            count(*) OVER 
              (PARTITION BY ts.train_service_key) AS train_records
FROM staging.train_service ts
   JOIN staging.portion_consist pc 
     USING (ds_code, train_service_key)
WHERE assembly_key = '185132';

bunun için çok daha az maliyetlidir:

SELECT *
FROM (SELECT DISTINCT ts.train_service_key,
            pc.assembly_key,
            count(*) OVER
              (PARTITION BY ts.train_service_key) AS train_records
FROM staging.train_service ts
   JOIN staging.portion_consist pc
     USING (ds_code, train_service_key)) AS query
WHERE assembly_key = '185132';

Umarım bu biraz daha spesifik ve yardımcı olur.

Son tecrübelerime göre (bu soruyu bulmama neden oluyor), yukarıdaki kabul edilen cevap tüm şartlar altında doğru değil. Pencere işlevini içeren nispeten basit bir sorgu var:

SELECT DISTINCT ts.train_service_key,
                pc.assembly_key,
                dense_rank() OVER (PARTITION BY ts.train_service_key
                ORDER BY pc.through_idx DESC, pc.first_portion ASC,
               ((CASE WHEN (NOT ts.primary_direction)
                 THEN '-1' :: INTEGER
                 ELSE 1
                 END) * pc.first_seq)) AS coach_block_idx
FROM (staging.train_service ts
JOIN staging.portion_consist pc USING (ds_code, train_service_key))

Bu filtreyi eklersem:

where assembly_key = '185132'

Aldığım açıklama planı şöyle:

QUERY PLAN
Unique  (cost=11562.66..11568.77 rows=814 width=43)
  ->  Sort  (cost=11562.66..11564.70 rows=814 width=43)
    Sort Key: ts.train_service_key, (dense_rank() OVER (?))
    ->  WindowAgg  (cost=11500.92..11523.31 rows=814 width=43)
          ->  Sort  (cost=11500.92..11502.96 rows=814 width=35)
                Sort Key: ts.train_service_key, pc.through_idx DESC, pc.first_portion, ((CASE WHEN (NOT ts.primary_direction) THEN '-1'::integer ELSE 1 END * pc.first_seq))
                ->  Nested Loop  (cost=20.39..11461.57 rows=814 width=35)
                      ->  Bitmap Heap Scan on portion_consist pc  (cost=19.97..3370.39 rows=973 width=38)
                            Recheck Cond: (assembly_key = '185132'::text)
                            ->  Bitmap Index Scan on portion_consist_assembly_key_index  (cost=0.00..19.72 rows=973 width=0)
                                  Index Cond: (assembly_key = '185132'::text)
                      ->  Index Scan using train_service_pk on train_service ts  (cost=0.43..8.30 rows=1 width=21)
                            Index Cond: ((ds_code = pc.ds_code) AND (train_service_key = pc.train_service_key))

Bu, tren servis masasındaki birincil anahtar endeksini veuple_consist tablosundaki benzersiz olmayan bir endeksi kullanıyor. 90ms'de çalıştırılır.

Bir görünüm oluşturdum (kesinlikle açık olması için buraya yapıştırarak ancak tam anlamıyla bir görünümdeki sorgu):

CREATE OR REPLACE VIEW staging.v_unit_coach_block AS
SELECT DISTINCT ts.train_service_key,
            pc.assembly_key,
            dense_rank() OVER (PARTITION BY ts.train_service_key
              ORDER BY pc.through_idx DESC, pc.first_portion ASC, (
                (CASE
              WHEN (NOT ts.primary_direction)
                THEN '-1' :: INTEGER
              ELSE 1
              END) * pc.first_seq)) AS coach_block_idx
 FROM (staging.train_service ts
  JOIN staging.portion_consist pc USING (ds_code, train_service_key))

Bu görünümü aynı filtreyle sorguladığımda:

select * from staging.v_unit_coach_block
where assembly_key = '185132';

Bu açıklama planı:

QUERY PLAN
Subquery Scan on v_unit_coach_block  (cost=494217.13..508955.10     rows=3275 width=31)
Filter: (v_unit_coach_block.assembly_key = '185132'::text)
 ->  Unique  (cost=494217.13..500767.34 rows=655021 width=43)
    ->  Sort  (cost=494217.13..495854.68 rows=655021 width=43)
          Sort Key: ts.train_service_key, pc.assembly_key, (dense_rank() OVER (?))
          ->  WindowAgg  (cost=392772.16..410785.23 rows=655021 width=43)
                ->  Sort  (cost=392772.16..394409.71 rows=655021 width=35)
                      Sort Key: ts.train_service_key, pc.through_idx DESC, pc.first_portion, ((CASE WHEN (NOT ts.primary_direction) THEN '-1'::integer ELSE 1 END * pc.first_seq))
                      ->  Hash Join  (cost=89947.40..311580.26 rows=655021 width=35)
                            Hash Cond: ((pc.ds_code = ts.ds_code) AND (pc.train_service_key = ts.train_service_key))
                            ->  Seq Scan on portion_consist pc  (cost=0.00..39867.86 rows=782786 width=38)
                            ->  Hash  (cost=65935.36..65935.36 rows=1151136 width=21)
                                  ->  Seq Scan on train_service ts  (cost=0.00..65935.36 rows=1151136 width=21)

Bu iki masada da tam tarama yapıyor ve 17 saniye sürüyor.

Bu konuya gelinceye kadar, libre olarak PostgreSQL ile görüşlerini kullanıyorum (kabul edilen cevapta ifade edilen yaygın görüşlerin anlaşıldığını anladım). Ayar döndürme işlevlerini kullanacağım toplu filtrelemeye ihtiyacım olursa, görünümleri kullanmaktan özellikle kaçınırdım.

Ayrıca PostgreSQL'deki CTE'lerin tasarım açısından kesinlikle ayrı ayrı değerlendirildiğinin farkındayım, bu yüzden bunları SQL Server ile aynı şekilde kullanmıyorum, örneğin alt sorgu olarak optimize edilmiş gibi görünüyorlar.

Bu nedenle cevabım, görüşlerin tam olarak dayandıkları sorgu kadar performans göstermediği durumlar olduğu için dikkatli olunması tavsiye edilir. PostgreSQL 9.6.6 tabanlı Amazon Aurora kullanıyorum.


2
Diğer cevaptaki uyarıya dikkat edin - " bunun basit bir ifade olması şartıyla ".
RDFozz

Bir yan not olarak, CASE WHEN (NOT ts.primary_direction) THEN '-1' :: INTEGER ELSE 1 ENDsırayla iki şartlandırma daha yazmaktan daha iyi olmanız durumunda sorguyu gerekenden daha yavaş yapmanıza gerek kalmayacaktır.
Evan Carroll,

@EvanCarroll Bir süre bununla mücadele ettim. CASE WHEN (NOT ts.primary_direction) THEN dense_rank() OVER (PARTITION BY ts.train_service_key ORDER BY pc.through_idx DESC, pc.first_portion ASC, pc.first_seq DESC) ELSE dense_rank() OVER (PARTITION BY ts.train_service_key ORDER BY pc.through_idx DESC, pc.first_portion ASC, pc.first_seq ASC) END AS coach_block_idx
CASE'i

Bu da iyi bir fikir değil. Burada birkaç probleminiz var. En büyüğü, görüşünüzün gerçekten mantıklı olmadığı ve sizin kullanımınızdan dolayı farklı şeyler yaptığı, dense_rank()yani performans sorunu değil.
Evan Carroll,

1
@EvanCarroll yorumunuz oraya kendim gitmemi istedi (bu nedenle düzenlenmiş cevabım). Teşekkür ederim.
enjayaitch

0

(Ben görünümler büyük bir hayranıyım değilim, ama burada PG ile çok dikkatli olmak zorunda ve ben kullanımına herkes PG da genel olarak görmektedir teşvik etmek istiyorum ve sorguları / kodun daha iyi anlaşılabilirliği ve daha iyi anlaşılması için )

Aslında ve ne yazık ki (UYARI :) Postgres'teki görüşleri kullanmak bize gerçek sorunlara neden oldu ve içinde kullandığımız özelliklere bağlı olarak performansımızı kötü bir şekilde düşürdü :-( (en azından v10.1 ile). (Bu diğerleriyle olmazdı). Oracle gibi modern DB sistemleri.)

Bu nedenle, muhtemelen (ve bu benim sorum budur) görüşe karşı herhangi bir filtreleme ... temel tablolara karşı tek bir sorgu yürütmesiyle sonuçlandı.

(Tam olarak neyi kastettiğinize bağlı olarak - hayır - ara sıcaklık tabloları, istemek istemediğiniz veya kestirimlerin üzerine basılmadığı durumlarda gerçekleşebilir ...)

En azından iki ana "özellik" olduğunu biliyorum; bu da Oracle'dan Postgres'e geçişlerin ortasında bizi yarı yolda bıraktı, bu yüzden bir projede PG'yi bırakmak zorunda kaldık:

  • CTEs ( with-clause alt sorgular / ortak tablo ifadeler yapılanması karmaşık sorguları (daha küçük uygulamalar) için yararlı) (genellikle), ancak PG olduğu gibi uygulanan tasarım "gizli" iyileştirici (örneğin olmayan endeksli geçici tablolar üreten) tavsiyeleri ve bu nedenle (benim için ve diğerleri için önemli olan) beyan edici SQL ( Oracle docu ) kavramını ihlal etmek :

    • basit sorgu:

      explain
      
        select * from pg_indexes where indexname='pg_am_name_index'
      
      /* result: 
      
      Nested Loop Left Join  (cost=12.38..26.67 rows=1 width=260)
        ...
        ->  Bitmap Index Scan on pg_class_relname_nsp_index  (cost=0.00..4.29 rows=2 width=0)
                                               Index Cond: (relname = 'pg_am_name_index'::name)
        ...
      */
    • bazı CTE kullanılarak yeniden yazıldı:

      explain
      
        with 
      
        unfiltered as (
          select * from pg_indexes
        ) 
      
        select * from unfiltered where indexname='pg_am_name_index'
      
      /* result:
      
      CTE Scan on unfiltered  (cost=584.45..587.60 rows=1 width=288)
         Filter: (indexname = 'pg_am_name_index'::name)
         CTE unfiltered
           ->  Hash Left Join  (cost=230.08..584.45 rows=140 width=260)  
      ...
      */
    • tartışmalarla diğer kaynaklar vb .: https://blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/

  • pencere fonksiyonları ile over-statements edilir potansiyel kullanılamaz (tipik olarak daha karmaşık sorgular göre raporları için bir kaynak olarak örneğin görüş, kullanılan)


için geçici çözümümüz with-clauslar çözümümüz

Tüm "satır içi görünümleri" özel bir ön ekle gerçek görünümlere dönüştüreceğiz, böylece görünümlerin listesini / ad alanını karıştırmazlar ve orijinal "dış görünüm" ile kolayca ilişkilendirilebilirler: - /


pencere fonksiyonları için çözümümüz

Oracle veritabanını kullanarak başarıyla uyguladık.


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.