私は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
操作がないため、なぜこの並べ替えが行われているのかわかりません。並べ替えは外部ディスクの並べ替えのようです。そのため、非常にコストがかかることが判明しています。
誰かがこの時点でこのクエリを最適化する正しい方向に私を向けることができますか?主なパフォーマンスのボトルネックを特定できたと思います。正しい方向へのプッシュと、この状況を改善するためのヒントが必要です。
クエリに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)
確かにプランナが合理的な選択をするのを助けてはいけません!