PostgreSQLのパフォーマンスにとってビューは有害ですか?


45

以下は、db設計に関する本からの抜粋です(データベース設計の開始ISBN:0-7645-7490-6):

ビューを使用する場合の危険は、ビューに対してクエリをフィルタリングし、非常に大きなテーブルの非常に小さな部分を読み取ることです。ビュー自体に対するフィルタリングは、ビュー内のクエリの実行が完了した後に適用されるため、ビュー内でフィルタリングを実行する必要があります。ビューは通常、開発プロセスの高速化に役立ちますが、長期的にはデータベースのパフォーマンスを完全に低下させる可能性があります。

以下は、PostgreSQL 9.5ドキュメントからの抜粋です。

ビューを自由に使用することは、優れたSQLデータベース設計の重要な側面です。ビューを使用すると、テーブル構造の詳細をカプセル化できます。これは、一貫したインターフェイスの背後で、アプリケーションの進化とともに変化する可能性があります。

2つのソースは互いに矛盾しているようです(「ビューを使用して設計しない」対「ビューを使用して設計する」)。

ただし、PGでは、ビューはルールシステムを使用して実装されます。そのため、おそらく(これが私の質問です)ビューに対するフィルタリングは、ビュー内のフィルターとして書き直され、その結果、基礎となるテーブルに対して1つのクエリが実行されます。

私の解釈は正しく、PGはビューに出入りするWHERE句を組み合わせますか?それとも、それらを次々に別々に実行しますか?短い自己完結型の正しい(コンパイル可能な)例はありますか?


両方の情報源が同じことについて話しているわけではないので、質問は正しくないと思います。1つ目はビューからのクエリに関連し、SELECT * FROM my_view WHERE my_column = 'blablabla';その後フィルターを適用します。2つ目はビューを使用して、それを使用するアプリケーションに対してデータモデルを透過的にすることです。最初のソースは、WHERE my_column = 'blablabla'ビュー定義内にフィルターを含めるように指示します。これにより、実行計画が改善されます。
EAmez

回答:


49

本は間違っています。

ビューからの選択は、基礎となるSQLステートメントの実行とまったく同じくらい高速または低速です。-を使用して簡単に確認できますexplain analyze

Postgresオプティマイザー(および他の多くの最新DBMSのオプティマイザー)は、ビューの述語を実際のビューステートメントにプッシュダウンできます-これが単純なステートメントである場合(これもを使用して検証できますexplain analyze)。

パフォーマンスに関する「悪い評判」は、ビューを使いすぎて、ビューを使用するビューを使用するビューの構築を開始したときから生じていると思います。非常に多くの場合、ビューを使用せずに手動で調整されたステートメントと比較して、過度のステートメントが発生します。たとえば、いくつかの中間テーブルが必要ないためです。ほとんどすべての場合、オプティマイザーはそれらの不要なテーブル/結合を削除したり、複数レベルのビューにわたって述語をプッシュダウンするほどスマートではありません(他のDBMSについても同様です)。


3
提案された反論のいくつかを考えると、あなたは簡単な声明とは何かについて少し説明したいかもしれません。
RDFozz

explain analyzeステートメントの使用方法を説明できますか?
ダスティンマイケルズ

@DustinMichels:マニュアルを
ご覧ください

19

あなたを与えるために、例えば、何の@a_horseについて説明を

Postgresは、標準化された形式でDBオブジェクトに関する情報提供する(場合によっては複雑な)ビューで構成される情報スキーマを実装します。これは便利で信頼性が高く、Postgresカタログテーブルに直接アクセスするよりもかなり高価になる可能性があります。


情報スキーマからテーブルのすべての可視列を取得する非常に簡単な例:

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

...システムカタログから:

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

クエリプランと両方の実行時間を比較しますEXPLAIN ANALYZE

  • 最初のクエリはview information_schema.columnsに基づいており、これはまったく必要のない複数のテーブルに結合します。

  • 2番目のクエリは1つのテーブルのみをスキャンするpg_catalog.pg_attributeため、はるかに高速です。(しかし、最初のクエリはまだ一般的なDBで数ミリ秒しか必要としません。)

詳細:


7

編集:

謝罪して、受け入れられた答えが常に正しいとは限らないという主張を撤回する必要があります-それは、ビューが常にサブクエリとして書かれたものと同じであると述べています。それは議論の余地のないことだと思うし、私の場合は何が起こっているのかがわかったと思う。

私は今、元の質問に対するより良い答えがあると思います。

元の質問は、ビューを使用するプラクティスをガイドする必要があるかどうかです(たとえば、2回以上保守する必要があるルーチンでSQLを繰り返すのとは対照的です)。

私の答えは、「クエリがウィンドウ関数またはサブクエリになるときにオプティマイザがクエリを異なる方法で処理する他のものを使用する場合ではありません。サブクエリを作成する行為(ビューとして表されるかどうか)実行時にパラメータでフィルタリングする場合。

私の窓関数の複雑さは不要です。これの説明計画:

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';

これよりもはるかに安価です:

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';

それがもう少し具体的で役立つことを願っています。

私の最近の経験(この質問を見つけるきっかけ)で、上記の受け入れられた答えはすべての状況下で正しくありません。ウィンドウ関数を含む比較的単純なクエリがあります:

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))

このフィルターを追加する場合:

where assembly_key = '185132'

私が得る説明計画は次のとおりです。

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))

これは、trainサービステーブルの主キーインデックスと、portion_consistテーブルの一意でないインデックスを使用しています。90msで実行されます。

ビューを作成しました(完全に明確にするためにここに貼り付けていますが、文字通りビュー内のクエリです)。

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))

同一のフィルターでこのビューを照会すると:

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

これは説明計画です。

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)

これは両方のテーブルでフルスキャンを実行しており、17秒かかります。

これに出くわすまで、私はPostgreSQLでビューを自由に使用してきました(受け入れられた回答で表明されている広く見られるビューを理解していました)。事前集計フィルタリングが必要な場合は、セットを返す関数を使用するため、特にビューの使用を避けます。

また、PostgreSQLのCTEは設計上、厳密に個別に評価されるため、SQL Serverの場合と同じようには使用しません。たとえば、サブクエリとして最適化されているようです。

したがって、私の答えは、ビューが基になっているクエリと正確に一致しない場合があるため、注意することをお勧めします。PostgreSQL 9.6.6に基づいたAmazon Auroraを使用しています。


2
他の答えの警告に注意しください-「これが簡単な文である場合」。
RDFozz

補足として、CASE WHEN (NOT ts.primary_direction) THEN '-1' :: INTEGER ELSE 1 ENDクエリを必要以上に遅くする必要はないので、順序でさらに2つの条件を記述するほうが良いでしょう。
エヴァンキャロル

@EvanCarroll私はこれにしばらく苦労しました。CASEを1レベルだけ引き出すほうがわずかに速いことがわかりました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
。– enjayaitch

これも良い考えではありません。ここでいくつか問題があります。大きなことは、あなたの見解は本当に意味をなさないことであり、あなたの使用のdense_rank()ために異なることをするので、実際にはパフォーマンスの問題ではありません。
エヴァンキャロル

1
@EvanCarrollのコメントは、私自身がそこにたどり着くように促しました(そのため、編集された答えです)。ありがとうございました。
-enjayaitch

0

(私はビューの大ファンですが、ここではPGに非常に注意する必要があります。クエリ/コードの理解性と保守性を高めるために、PGでも一般的にビューを使用することをお勧めします)

実際、悲しいことに(警告:) Postgresでビューを使用すると、実際に問題が発生し、その内部で使用している機能によってパフォーマンスが大幅に低下しました:- ((少なくともv10.1では)。 Oracleのような最新のDBシステム。)

したがって、ビューに対するフィルタリング(おそらく、これが私の質問です)...基になるテーブルに対する単一のクエリ実行になります。

(正確に何を意味するかによって-いいえ-なりたくない中間的な一時テーブルが具体化されるか、述語がプッシュダウンされない場所に...)

OracleからPostgresへの移行の途中で私たち失望させた少なくとも2つの主要な「機能」を知っているので、プロジェクトでPGを放棄しなければなりませんでした。

  • CTEwith-clauseサブクエリ/ 共通テーブル式)は(通常)より複雑なクエリの構造化(小規模なアプリケーションでも)に役立ちますが、PG では「隠された」オプティマイザヒントとして実装されます(インデックス化されていない一時テーブルなど)そのため、宣言型SQLOracle docu)の概念(私にとっても重要な他の多くのもの)にも違反しています。例えば

    • 簡単なクエリ:

      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)
        ...
      */
    • CTEを使用して書き直されました。

      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)  
      ...
      */
    • ディスカッションなどのさらなるソース:https : //blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/

  • over-statementsを使用したウィンドウ関数使用できない可能性 があります(通常、ビューで、たとえば、より複雑なクエリに基づくレポートのソースとして使用されます)


with-clausesの回避策

すべての「インラインビュー」を特別なプレフィックスを持つ実際のビューに変換して、ビューのリスト/ネームスペースを混乱させず、元の「外部ビュー」に簡単に関連付けることができるようにします。


ウィンドウ関数のソリューション

Oracleデータベースを使用して正常に実装しました。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.