web-dev-qa-db-ja.com

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

大きなテーブル(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句はサブクエリの前に実行されますか?より高速な実行を行うために、他の句の大きなループを回避するために、最も制限の多い句を最初に置くのは良い方法ですか?

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

38

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

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

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

  • 結合が書き込まれる順序も、構成されたjoin_collapse_limitまで無視されます。それ以上の結合がある場合は、記述された順序で実行されます。

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

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

PostgreSQLがクエリを変換します

PostgreSQLは、まったく同じ効果を維持しながらクエリを大幅に変換し、結果を変更せずに高速に実行できるようにします。

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

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

  • サブクエリは、外部テーブルの結合にフラット化できます。 EXISTSクエリやNOT 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は通常プルアップ/プッシュダウン/フラット化の決定について正しいですが、常にではないので、時々あなたは(ab)CTEまたはOFFSET 0ハック。そのような場合は、クエリプランナーのバグを報告してください。


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

72
Craig Ringer

SQLは宣言型言語です。方法ではなく、必要なことを伝えます。 RDBMSは、実行プランと呼ばれるクエリの実行方法を選択します。

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

ほとんどの場合、これは本当に最良の方法ですが、DBエンジンが誤った選択をして、クエリが非常に遅くなることがあります。

17
alci