web-dev-qa-db-ja.com

マージ結合を使用したクエリはひどく遅い

私はPostgresデータベースを使用しており、次のクエリを最適化しようとしています:

SELECT DISTINCT catalogite1_.id 
   FROM            cat_catalogitem catalogite1_ 
   INNER JOIN      cat_service service2_ 
   ON              catalogite1_.service_id=service2_.id 
   LEFT OUTER JOIN cat_entitlement_services entitledse6_ 
   ON              service2_.id=entitledse6_.service_id 
   LEFT OUTER JOIN cat_entitlement entitlemen7_ 
   ON              entitledse6_.entitlement_id=entitlemen7_.id 
   AND             (entitlemen7_.id IN ('505e03e5-7370-42c2-a26e-bdb2df593934' , 
                   '508da3b6-7147-4b16-971f-6e6476b8ef44' , 
                   '6c68fbd2-7cc4-4b7c-85c1-617b69578ab9' , 
                   '6c9e5ff0-a073-4923-a5ec-b47f5e4c120a' , 
                   '961bee54-e9d6-402c-a763-c3937b03402f' , 
                   '2f113c9a-9e2f-47d8-beda-df0e05faa167' , 
                   '471bca1e-a112-4842-bdfc-252b8848b862' , 
                   '482ba515-2197-4fdb-a74b-37d9a0795c4e' , 
                   '872038e4-766a-4b93-bf95-aa2735e7f942' , 
                   'fd6345fc-8799-42a0-83d5-0234e450e397' , 
                   '7378e830-5271-482f-b73b-7ce4232b000d' , 
                   '6aeafe3b-aac9-4c67-8895-aa77da7a7d6b')) 

       LEFT OUTER JOIN cat_subtenant catalogsub3_ 
   ON              catalogite1_.subtenant_id=catalogsub3_.id 
   LEFT OUTER JOIN cat_entitlement_catalogitems entitledca4_ 
   ON              catalogite1_.id=entitledca4_.catalogitem_id 
   LEFT OUTER JOIN cat_entitlement entitlemen5_ 
   ON              entitledca4_.entitlement_id=entitlemen5_.id 
   AND             (entitlemen5_.id IN ('505e03e5-7370-42c2-a26e-bdb2df593934' , 
                   '508da3b6-7147-4b16-971f-6e6476b8ef44' , 
                   '6c68fbd2-7cc4-4b7c-85c1-617b69578ab9' , 
                   '6c9e5ff0-a073-4923-a5ec-b47f5e4c120a' , 
                   '961bee54-e9d6-402c-a763-c3937b03402f' , 
                   '2f113c9a-9e2f-47d8-beda-df0e05faa167' , 
                   '471bca1e-a112-4842-bdfc-252b8848b862' , 
                   '482ba515-2197-4fdb-a74b-37d9a0795c4e' , 
                   '872038e4-766a-4b93-bf95-aa2735e7f942' , 
                   'fd6345fc-8799-42a0-83d5-0234e450e397' , 
                   '7378e830-5271-482f-b73b-7ce4232b000d' , 
                   '6aeafe3b-aac9-4c67-8895-aa77da7a7d6b')) 

   WHERE           catalogite1_.is_requestable=true 
   AND             catalogite1_.status='PUBLISHED' 
   AND             catalogite1_.tenant_id='intel-1' 
   AND             ( 
                                   service2_.id=NULL 
                   OR              COALESCE(NULL) IS NULL) 
   AND             service2_.status='ACTIVE' 
   AND             service2_.tenant_id='intel-1' 
   AND             ((entitlemen7_.id IS NOT NULL)
   AND             (catalogsub3_.id IS NULL
                   OR entitlemen7_.subtenant_id=catalogsub3_.id)
   AND             (entitlemen5_.id IS NULL
                   OR entitlemen5_.subtenant_id<>entitlemen7_.subtenant_id)
   OR              (entitlemen5_.id IS NOT NULL)
   AND             (catalogsub3_.id IS NULL
                   OR entitlemen5_.subtenant_id=catalogsub3_.id)
   AND             entitledca4_.is_hidden=false)

ご覧のとおり、クエリは非常に複雑で、複数の結合とフィルタリング条件を利用しています。私の問題は、このクエリが完了するまでに60秒以上かかることです。私の目標は、パフォーマンスを2〜3秒に下げることです。

私はいくつかの背景調査を行い、PostgresのEXPLAIN ANALYZE機能を介してこのクエリを実行することを検討しました。これにより、ボトルネックがどこにあるかについていくつかの素晴らしい洞察が得られました。

EXPLAIN ANALYZEの出力の読みやすい表現へのリンクは次のとおりです。

https://explain.depesz.com/s/f3hn

ご覧のとおり、主なボトルネックは、データベースが3億1700万行をフィルターで除外しているマージ結合にあるようです!もう1つのボトルネックは、マージ結合の前のいくつかのステップでも発生するソートです。クエリにORDER BY操作がないため、なぜこの並べ替えが行われているのかわかりません。並べ替えは外部ディスクの並べ替えのようです。そのため、非常にコストがかかることが判明しています。

誰かがこの時点でこのクエリを最適化する正しい方向に私を向けることができますか?主なパフォーマンスのボトルネックを特定できたと思います。正しい方向へのプッシュと、この状況を改善するためのヒントが必要です。

2
NewGradDev

クエリにORDER BY操作がないため、なぜこの並べ替えが行われているのかわかりません。

マージ結合ができるようにソートしています。マージ結合にはソートされた入力が必要です。

並べ替えは外部ディスクの並べ替えのようです。そのため、非常にコストがかかることが判明しています。

いいえ、実際の並べ替えにはそれほど時間はかかりません(とにかくwork_memを増やしたいと思うかもしれませんが、そのような並べ替えはおそらくディスク上にある必要はありません。現在の設定は何ですか?)。ただし、ソートされたデータを取得したら、マージ結合の一部としてそのデータを何度も再プローブする必要があります。それは時間が経過しているところであり、その時間の一部はソートステップに起因します。また、この種の計画では、EXPLAIN ANALYZEのレポートにかかる時間を収集するオーバーヘッドが非常に大きくなる可能性があり、クエリが監視されていない場合よりもクエリが数倍長くかかります。 EXPLAIN(ANALYZE、TIMING OFF)を実行する場合、最終的な実行時間に対して何が得られますか?

マージ結合ではなくハッシュ結合を使用する場合、別のメカニズムを使用するだけで再プローブを実行する必要があるため、おそらく何も変更されません。

可能性のある問題は、クエリが2つのサブブランチとして実行されていることです。1つはcatalogite1_から、もう1つはservice2_から、それらは最後に実質的にデカルト結合されます。比較に必要なデータの一部は1つのブランチからのものであり、一部は別のブランチからのものであるため、フィルタリングは最後まで実行できません。また、service2_には適格な行が1つしかないため、実質的にデカルト結合になります。つまり、catalogite1_.service_id=service2_.idはあまり選択的ではありません

クエリのこの部分を変更してみます。

ON              service2_.id=entitledse6_.service_id

これに:

ON              catalogite1_.service_id=entitledse6_.service_id

これにより、クエリのはるかに低い場所でフィルタリングを行うことができます。これが機能する場合、プランナーがこの切り替えを行わなかった理由を知ることは興味深いでしょう-可能でなければなりません。 join_collapse_limitの設定は何ですか?

また、次のようなもの:

AND             ( 
                                   service2_.id=NULL 
                   OR              COALESCE(NULL) IS NULL) 

確かにプランナが合理的な選択をするのを助けてはいけません!

3
jjanes