WHERE句は、記述された順に適用されますか?


36

大きなテーブル(3700万行)を調べるクエリを最適化しようとしていますが、クエリで操作が実行される順序について質問があります。

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

されているWHERE日付範囲の句は、サブクエリの前に実行しますか?より高速な実行を行うために、他の句の大きなループを回避するために、最も制限の厳しい句を最初に置くのは良い方法ですか?

現在、クエリの実行には非常に時間がかかります。

回答:


68

@alciの答えを詳しく説明するには:

PostgreSQLは、あなたが物事を書く順番を気にしません

  • PostgreSQLは、WHERE句のエントリの順序をまったく考慮せず、コストと選択性の推定のみに基づいてインデックスと実行順序を選択します。

  • 結合が書き込まれる順序も、構成されている限り無視されますjoin_collapse_limit。それより多くの結合がある場合、記述された順序で実行します。

  • サブクエリは、外部クエリが実際に情報を必要とする前に実行される限り、最速のものに応じて、サブクエリを含むクエリの前または後に実行できます。多くの場合、サブクエリは途中で実行されるか、外部クエリとインターリーブされます。

  • PostgreSQLが実際にクエリの一部を実行するという保証はありません。それらは完全に最適化されます。これは、副作用のある関数を呼び出す場合に重要です。

PostgreSQLはクエリを変換します

PostgreSQLは、結果を変えずに実行を高速化するために、まったく同じ効果を保持しながらクエリを大幅に変換します。

  • サブクエリの外部の用語はサブクエリにプッシュダウンされるため、外部クエリで記述した場所ではなく、サブクエリの一部として実行されます。

  • サブクエリ内の用語は、外部クエリにプルアップできるため、サブクエリで記述した場所ではなく、外部クエリの一部として実行されます

  • サブクエリは、通常、外部テーブルの結合にフラット化できます。同じことがクエリEXISTSNOT EXISTSクエリにも当てはまります。

  • ビューは、ビューを使用するクエリに統合されます

  • SQL関数は、呼び出し元のクエリにインライン化されることがよくあります

  • ...また、定数式の事前評価、一部のサブクエリの非相関、その他のプランナー/オプティマイザトリックなど、クエリに対して行われる他の多数の変換があります。

一般に、PostgreSQLはクエリを大規模に変換および書き換えて、これらの各クエリを実行できます。

select my_table.*
from my_table
left join other_table on (my_table.id = other_table.my_table_id)
where other_table.id is null;

select *
from my_table
where not exists (
  select 1
  from other_table
  where other_table.my_table_id = my_table.id
);

select *
from my_table
where my_table.id not in (
  select my_table_id
  from other_table
  where my_table_id is not null
);

通常、すべてがまったく同じクエリプランを生成します。(とにかく上記で愚かな間違いをしなかったと仮定)。

クエリを最適化しようとするのは珍しいことではありません。クエリプランナーが試行しているトリックを既に見つけ出し、それらを自動的に適用しているため、手動で最適化したバージョンは元のバージョンよりも優れています。

制限事項

プランナー/オプティマイザーは、決して無名ではなく、クエリの効果を変更できないこと、決定に使用できるデータ、実装されているルール、CPU時間を絶対に変更できないという要件によって制限されます。最適化を熟考するのに費やす余裕があります。例えば:

  • プランナーはANALYZE(通常は自動バキューム経由で)保持される統計情報に依存します。これらが古くなっている場合、計画の選択が悪い場合があります。

  • 統計はサンプルにすぎないため、特にサンプルが少なすぎる場合は、サンプリング効果により誤解を招く可能性があります。計画の選択が不適切になる可能性があります。

  • 統計は、列間の相関など、テーブルに関する特定の種類のデータを追跡しません。これにより、プランナーは、物事が独立していない場合に独立していると想定した場合に、悪い決定を下すことになります。

  • プランナーは、random_page_costインストールされている特定のシステムでのさまざまな操作の相対速度を伝えるなど、コストパラメーターに依存しています。これらは単なるガイドです。彼らがひどく間違っている場合、彼らは貧弱な計画の選択につながる可能性があります。

  • LIMITまたはを含むサブクエリは、OFFSETフラット化できないか、プルアップ/プッシュダウンの対象になりません。これは、しかし、あるいはそれが実行だろうと、外部クエリのすべての部分の前に実行しますという意味ではありませんまったく

  • CTE用語(WITHクエリ内の句)は、実行された場合、常に完全に実行されます。それらをフラット化することはできず、用語をCTEの用語バリアを超えてプッシュまたはプルダウンすることはできません。CTE用語は、常に最終クエリの前に実行されます。これは非SQL標準の動作ですが、PostgreSQLの動作として文書化されています。

  • PostgreSQLには、外部テーブル、security_barrierビュー、および特定の他の特別な種類のリレーションに対するクエリ全体を最適化する制限された機能があります

  • PostgreSQLは、プレーンSQL以外で記述された関数をインライン化しません。また、外部クエリとのプルアップ/プッシュダウンも行いません。

  • プランナー/オプティマイザーは、式のインデックスの選択、およびインデックスと式の間のデータタイプの些細な違いについて本当に愚かです。

トン以上。

あなたの質問

クエリの場合:

select 1 
from workdays day
where day.date_day >= '2014-10-01' 
    and day.date_day <= '2015-09-30' 
    and day.offer_id in (
        select offer.offer_day 
        from offer  
        inner join province on offer.id_province = province.id_province  
        inner join center cr on cr.id_cr = province.id_cr 
        where upper(offer.code_status) <> 'A' 
            and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
            and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
    )

追加の結合セットを使用した単純なクエリへのフラット化を妨げるものは何もありません。

おそらく次のようなものになるでしょう(明らかに、テストされていません):

select 1 
from workdays day
inner join offer on day.offer_id = offer.offer_day
inner join province on offer.id_province = province.id_province  
inner join center cr on cr.id_cr = province.id_cr 
where upper(offer.code_status) <> 'A' 
   and province.id_region in ('10' ,'15' ,'21' ,'26' ,'31' , ...,'557') 
   and province.id_cr in ('9' ,'14' ,'20' ,'25' ,'30' ,'35' ,'37')
   and day.date_day >= '2014-10-01' 
   and day.date_day <= '2015-09-30';

PostgreSQLは、選択性と行数の推定、および利用可能なインデックスに基づいて、結合順序と結合方法を最適化します。これらが現実を合理的に反映している場合、結合を実行し、最適な順序でwhere句エントリを実行します-多くの場合、それらを一緒に混合するので、これを少し実行してから、最初の部分に戻りますなど

オプティマイザーが何をしたかを見る方法

PostgreSQLがクエリを最適化するSQLを見ることができません。SQLを内部クエリツリー表現に変換してから変更するためです。あなたはすることができますクエリプランをダンプし、他のクエリと比較します。

そのクエリプランまたは内部プランツリーをSQLに「逆解析」する方法はありません。

http://explain.depesz.com/には、適切なクエリプランヘルパーがあります。クエリプランなどにまったく慣れていない場合(この場合、この記事を読んで驚くことになります)、PgAdminにはグラフィカルなクエリプランビューアーがあり、情報ははるかに少なくなりますが簡単です。

関連資料:

プッシュダウン/プルアップおよびフラット化機能は、各リリースで引き続き改善されます。PostgreSQLは通常、プルアップ/プッシュダウン/フラット化の決定について正しいですが、常にではないため、CTEまたはOFFSET 0ハックを(ab)使用しなければならない場合があります。そのような場合は、クエリプランナーのバグを報告してください。


もしあなたが本当に本当に熱心なら、debug_print_plansオプションを使って生のクエリプランを見ることができますが、私はそれを読みたくないと約束します。本当に。


うわー、完全な答え:-) Postgresql(およびOracleのような他の有名なDBエンジン)で遅いプランを持っていたケースの1つは、列間の相関または複数の相関結合です。計画のこの時点で実際には数千行あるのに数行しかないと考えて、ネストされたループを実行することがよくあります。この種のクエリを最適化する1つの方法は、「set enable_nestloop = off;」を設定することです クエリの期間中。
ALCI

私は、v9.5.5がTO_DATEを適用できるかどうかをチェックする前に、単純な7つのwhere句クエリでTO_DATEを適用しようとした状況に遭遇しました。順序が重要です。
user1133275

@ user1133275その場合、計算コストの見積もりが同じだったため、偶然しかうまくいきませんでした。PostgreSQLは、それto_date以降のバージョンのチェックの前に、またはオプティマイザー統計の変更のために、実行を決定する可能性があります。するには、確実にのみチェック後に実行する必要がある関数の前にチェックを実行し、使用してCASE文を。
クレイグリンガー

SO 見た最大の答えの1つです。いいね!
62mkv

単純なクエリの追加order byにより、クエリがない場合よりもクエリの実行がはるかに高速になる状況を経験しましたorder by。それが私がクエリを実行したいような方法で結合でクエリを書く理由の1つです-素晴らしいオプティマイザを持っているのは良いことですが、結果に完全にあなたの運命を信頼し、方法を考えずにクエリを書くことは賢明ではないと思いますそれはmay be実行されました...素晴らしい答え!!
Greg0ry

17

SQLは宣言型言語です。どのように行うかではなく、何を望むかを伝えます。RDBMSは、実行プランと呼ばれるクエリの実行方法を選択します。

むかしむかし(5〜10年前)、クエリの記述方法は実行計画に直接影響しましたが、今日では、ほとんどのSQLデータベースエンジンはコストベースオプティマイザーを使用して計画を立てています。つまり、データベースオブジェクトの統計に基づいてクエリを実行するためのさまざまな戦略を評価し、最適な戦略を選択します。

ほとんどの場合、それは実際には最良のものですが、DBエンジンが不適切な選択を行い、クエリが非常に遅くなることがあります。


一部のRDBMSではクエリの順序が依然として重要であることに注意する必要がありますが、より高度なクエリでは、理論だけでなく実際にもあなたの言うことはすべて真実です。クエリプランナーが実行順序の不適切な選択を選択している場合、通常、より効率的な方向にプッシュするためのクエリヒントが利用できます(WITH(INDEX(<index>))MSSQLで特定の結合のインデックスの選択を強制する場合など)。
デビッドスピレット

問題は、上のインデックスがdate_day実際に存在するかどうかです。存在しない場合、オプティマイザーには比較する計画が多くありません。
jkavalik
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.