最適ではない計画で曲がったクエリを記述し、DBMSに考えさせる方法

すべてがシンプルです。 ここでは、PostgreSQLの場合の「ダミーのクエリ解析の基本」と、PostgreSQLおよびMySQLでクエリを記述する必要がない方法と、すべてを同じように記述した場合の動作に関する本番の素晴らしい非発明の例があります。







はじめに



人やドキュメントにかなりよく説明されているいくつかの明らかなことについてお話します。これらは通常、1ダースの熊手を入力した後、または一定数の手足を撃った後、または他の自傷行為の後に読みます。



いくつかの部分があり、論理的には相互に疎結合ですが、それらはすべて、ビジネスのさまざまなニーズを解決する過程で満たされ、何らかの形でそのニーズを満たしました。



と働くことのニュアンス



同じことは、大きな意味の負荷のない構文糖に非常に似ており、マーティンとファウラーの精神で大きな足布を別々の方法に分解することに関連付けられています。 ここでの主な機能は、特にクエリの最適化に関しては、これがメソッド/関数の類似物ではないということです。



私はすぐに読者に謝罪しますが、本文の後半では根本的に重要なリクエストのみが掲載され、リクエスト全体が公開されるわけではありません。 第一に、データ構造の機能に煩わされないようにするため、そして第二に、私が誤って私企業を投稿しないようにするためです。 ピースが完全に読めない場合は、強く叩かないでください。しかし、それらを改良する方法を提案してください。 ありがとう



方法は必要ありません。



クエリの本体からのソースSQLスライス
LEFT JOIN specifications_history AS specification_history ON specification_history.id = specification_detail.entity_history_id AND specification_history.specification_id = ANY(specification_parts.ids) LEFT JOIN specification_revision_details AS specification_section_detail ON specification_section_detail.specification_revision_id = specification_revision.id AND specification_section_detail.entity_type = 1002 LEFT JOIN specification_sections_history AS specification_section_history ON specification_section_history.id = specification_section_detail.entity_history_id LEFT JOIN specification_revision_details AS section_item_detail ON section_item_detail.specification_revision_id = specification_revision.id AND section_item_detail.entity_type = 1003 LEFT JOIN section_items_history AS section_item_history ON section_item_history.id = section_item_detail.entity_history_id
      
      







「洗練された」リクエスト
 WITH revision_products AS ( SELECT DISTINCT specification_revision.id AS revision_id, specification_history.specification_id AS specification_id, section_item_history.product_id AS product_id FROM specification_revisions AS specification_revision INNER JOIN specification_revision_details AS specification_detail ON specification_detail.specification_revision_id = specification_revision.id AND specification_detail.entity_type = 1001 INNER JOIN specifications_history AS specification_histor ON specification_history.id = specification_detail.entity_history_id INNER JOIN specification_revision_details AS specification_section_detail ON specification_section_detail.specification_revision_id = specification_revision.id AND specification_section_detail.entity_type = 1002 INNER JOIN specification_sections_history AS specification_section_history ON specification_section_history.id = specification_section_detail.entity_history_id INNER JOIN specification_revision_details AS section_item_detail ON section_item_detail.specification_revision_id = specification_revision.id AND section_item_detail.entity_type = 1003 INNER JOIN section_items_history AS section_item_history ON section_item_history.id = section_item_detail.entity_history_id WHERE section_item_history.product_id IS NOT NULL )
      
      







ここでは、次のことが起こりました。多くのLEFT JOINがあるリクエストの本文から、WITHで削除され、INNER JOINに変わりました。 本体の読みやすさを向上させるために、ピースには調和のとれた名前が付けられ、実装の詳細はすべて引き離されました。 きれいなコードプラクティスを最大限に。 読みやすさで、本当に良くなりました。 リクエストの本文には、10の代わりに5つの結合が残っています。これは、リクエストの速度が75ミリ秒から95秒にすぐに低下したことです。 次のような興味深いものを紹介します:



  -> Unique (cost=796821.66..848031.33 rows=5120967 width=12) (actual time=80769.666..94946.622 rows=315260 loops=1) -> Sort (cost=796821.66..809624.07 rows=5120967 width=12) (actual time=80769.663..90662.993 rows=37659600 loops=1) Sort Key: specification_revision_1.id, specification_history.specification_id, section_item_history.product_id Sort Method: external merge Disk: 809888kB
      
      





つまり、誰かが37リャモフの行を取り、1ギガのメモリ内でそれらを元気に並べ始めました。 すぐに質問が発生しました:





回答 :本体からWITHに結合を転送すると、 ドキュメントに記載されているとおりになりました。



WITHクエリ(共通テーブル式)
WITHクエリの有用な特性は、親クエリまたは兄弟WITHクエリによって複数回参照される場合でも、親クエリの実行ごとに1回だけ評価されることです。 したがって、複数の場所で必要な高価な計算をWITHクエリ内に配置して、冗長な作業を回避できます。 別の可能なアプリケーションは、副作用のある関数の望ましくない多重評価を防ぐことです。 ただし、このコインのもう1つの側面は、オプティマイザーが通常のサブクエリよりも親クエリからWITHクエリに制限をプッシュできないことです。 通常、WITHクエリは、親クエリが後で破棄する可能性のある行を抑制することなく、記述されたとおりに評価されます。 (ただし、前述のように、クエリへの参照が限られた数の行のみを要求する場合、評価が早期に停止する可能性があります。)



要するに、WITHクエリは1回実行され、多くの場合、最適化されていません。つまり、使用される場所は実行計画に影響しません。



つまり、リクエストの一部を独立した部分のレベルに残し、そこから重要な条件を追加することを忘れて、ほとんどのトマトの選択を減らしました。 その結果、彼らはベース全体を燃やしてから、このモンスターを本体に渡し、そこから数十行を取り出しました。



上記の特定のケースでは、WHEREには「product_id = 1234」という形式の条件があり、これによりメインデータの制限が設定されました。 この条件がWITHにドラッグされた場合、すべてがほぼ同じ速度で動作し続けます。 ただし、これは条件の右側の静的な値の場合にのみ実行できます。 たとえば、再帰クエリ中に理想的なクエリを取得した場合、WITHを使用するとこのような状態は解消されず、クエリを断片に分割するというアイデアは恥ずかしく遅くなります。



結論





可視化の説明



Explain.depesz.comで誰もが最新の状態にあると思います。 リクエストの何が問題なのかを美しく示しています。







実際、これはExplainコマンドのデフォルト出力の単なる色付けですが、最初は非常に明確で、特に何に注目すべきかがわかるまで役立ちます...



ここでは、各列にいくつかの単語を付けて、それらが実行結果にどのように影響するかを説明します。 そうです、それはhelpにそこに書かれていますが 、スナップするまでヘルプを読む人はほとんどいません。





初心者向けオプティマイザーのヒント



すべてが遅くなり、どこから始めるべきかわからない場合は、ここにいくつかのヒントがあります。 前の段落から色付きの説明を(できれば分析を使用して)取得し、それを見てください。 ほとんどの場合、問題(読み取り80%+ランタイム)は、実行計画で説明されている操作の1つに集中しています。 つまり、Exclusive / Inclusiveによって、最も暗い場所と最も暗い場所を見つけます。 繰り返しますが、上記の例は、クエリが実行される合計95秒のうち、uniq操作が94秒続くことを示しています。 同じ場所で、uniqソートではほとんどすべての時間がかかり、90秒かかります。 ここでは、行数、ソートアルゴリズム、および使用メモリの形式で問題を確認できます。 残っているのは、「誰が責任を負い、何をすべきか」を理解することだけです。 ここでは、ターゲットデータベースのデータ構造とクエリ結果の要件に関する知識のみが役立ちます。 数行を再配置するか、追加の条件を追加するだけで十分な場合があります。また、元の形式でできることは速度を落とすことだけなので、要求を完全に書き換える必要がある場合があります。



大きな「Rows X」にも注意を払う価値があります。 これは、予測および実際のサンプリング結果の欠落を示しており、ほとんどの場合、テーブルの統計が不十分であることが原因です。 これにより、最適でないクエリ実行プランが作成される場合があります。 たとえば、100万個の要素を持つテーブルから1つの行を選択します。 スケジューラが選択の結果が1行ではなく〜200,000になると判断した場合、インデックスで検索するのではなく、フルスキャンを実行します。これは、結果の行とテーブルのサイズの比率に対する最適な戦略であるためです。 速度について自分で結論を導きます。



標準レーキ



以下は、実際に最も頻繁に発生したものであり、クエリのわいせつな動作を引き起こしたものです。





終わり



どうもありがとう。 あなたがあなたが必要としない方法のあなた自身の素晴らしい例を持っているなら、静かにしないでください、多くの「有害なヒント」と「生きている例」はありません。 そして、この記事は、試験のチケットのように、話す口実に過ぎません。



All Articles