質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
PostgreSQL

PostgreSQLはオープンソースのオブジェクトリレーショナルデータベース管理システムです。 Oracle Databaseで使われるPL/SQLを参考に実装されたビルトイン言語で、Windows、 Mac、Linux、UNIX、MSなどいくつものプラットフォームに対応しています。

Q&A

2回答

1681閲覧

クエリコストの見方 ポスグレ

riomakopa

総合スコア30

PostgreSQL

PostgreSQLはオープンソースのオブジェクトリレーショナルデータベース管理システムです。 Oracle Databaseで使われるPL/SQLを参考に実装されたビルトイン言語で、Windows、 Mac、Linux、UNIX、MSなどいくつものプラットフォームに対応しています。

0グッド

0クリップ

投稿2019/01/07 07:02

編集2019/01/07 07:37

やりたいことはクエリコストの改善なのですが
考え方を教えて頂きたいです。
ゴールは「適切にINDEXが張られているか」と
どこでコストが掛かっているのかを解明したいです。

クエリで一番コストがかかっているところは
以下の所だと思っているのですが
あってますでしょうか?

__Append (cost=277.15..1200.83 rows=14 width=204) (actual time=12.277..253.460 rows=678 loops=1) -> Nested Loop Left Join (cost=277.15..1198.78 rows=1 width=93) (actual time=12.275..252.529 rows=664 loops=1) -> Nested Loop Left Join (cost=277.01..1198.60 rows=1 width=76) (actual time=12.266..247.347 rows=664 loops=1) -> Nested Loop (cost=276.87..1198.38 rows=1 width=54) (actual time=12.257..242.928 rows=664 loops=1) -> Nested Loop Left Join (cost=276.59..1197.17 rows=1 width=48) (actual time=12.240..236.827 rows=664 loops=1) -> Nested Loop (cost=268.15..1182.94 rows=1 width=52) (actual time=11.120..35.718 rows=664 loops=1) __**

理由は、costが200以上掛かっているためです。
(コストは1以上あってはならないという認識があります)
結果的にcost=10988.02..10988.07 になっているように思います。
こちらは、先輩方に教えていただいたことですが
https://lets.postgresql.jp/sites/default/files/2016-11/Explaining_Explain_ja.pdf
↑の中に
「値のシーケンシャルI/Oで1ページを読みこむコストを1とした際の相対値で示される」とあります

Sort (cost=10988.02..10988.07 rows=17 width=1394) (actual time=3605.324..3608.863 rows=3790 loops=1) Sort Key: mrc.reg_time, mm.message_id Sort Method: external merge Disk: 3184kB CTE tenant_post_detail_list -> ** __Append (cost=277.15..1200.83 rows=14 width=204) (actual time=12.277..253.460 rows=678 loops=1) -> Nested Loop Left Join (cost=277.15..1198.78 rows=1 width=93) (actual time=12.275..252.529 rows=664 loops=1) -> Nested Loop Left Join (cost=277.01..1198.60 rows=1 width=76) (actual time=12.266..247.347 rows=664 loops=1) -> Nested Loop (cost=276.87..1198.38 rows=1 width=54) (actual time=12.257..242.928 rows=664 loops=1) -> Nested Loop Left Join (cost=276.59..1197.17 rows=1 width=48) (actual time=12.240..236.827 rows=664 loops=1) -> Nested Loop (cost=268.15..1182.94 rows=1 width=52) (actual time=11.120..35.718 rows=664 loops=1) __** Planning time: 13.235 ms Execution time: 3611.990 ms

SQL

explain analyze

WITH tenant_post_detail_list AS (SELECT u.post_id AS post_id ,u.user_id AS user_id ,ct.tenant_code AS tenant_code ,ct.tenant_name AS tenant_post_name ,z.zone_id AS zone_id ,z.zone_name AS zone_name ,cg.category_id AS category_id ,cg.category_name AS category_name FROM .v_max_revision_all_tenant AS mrt INNER JOIN .m_contract AS ct ON mrt.tenant_code = ct.tenant_code AND mrt.revision = ct.revision INNER JOIN .m_tenant AS t ON mrt.tenant_code = t.tenant_code AND mrt.revision = t.revision INNER JOIN m_user AS u ON ct.user_id = u.user_id LEFT OUTER JOIN ( SELECT cp.compartment_id ,cp.zone_id ,cp.floor_id FROM .m_compartment AS cp INNER JOIN ( SELECT compartment_id ,MAX(use_to) AS max_use_to FROM .m_compartment WHERE 'now' BETWEEN use_from AND use_to GROUP BY compartment_id ) AS cp2 ON cp2.compartment_id=cp.compartment_id AND cp2.max_use_to=cp.use_to ) AS cp ON ct.compartment_id = cp.compartment_id LEFT OUTER JOIN .m_zone AS z ON cp.zone_id = z.zone_id LEFT OUTER JOIN .m_category AS cg ON t.category_id1 = cg.category_id AND cg.category_kbn = 1 UNION ALL SELECT p.post_id AS post_id ,' ' AS user_id ,' ' AS tenant_code ,p.post_name AS tenant_post_name ,' ' AS zone_id ,' ' AS zone_name ,' ' AS category_id ,' ' AS category_name FROM m_post AS p WHERE (p.sc_id = 'marinoa' OR p.sc_id = 'ALL') AND p.del_flg = 0 ) SELECT mm.* ,mrc.message_receive_id ,mrc.reg_time AS receive_date ,tpl.tenant_post_name ,mrp.reply_no ,(CASE WHEN mm.confirm_req_flg = 1 AND mrc.confirm_flg = 0 THEN 0 WHEN mm.confirm_req_flg = 0 AND mrc.read_flg = 0 THEN 1 WHEN (mm.confirm_req_flg = 1 AND mrc.confirm_flg = 1) OR (mm.confirm_req_flg = 0 AND mrc.read_flg = 1) THEN 2 END ) AS state ,reply_check.max_reply_message_id IS NOT NULL AS replied_flg FROM .t_message_receive AS mrc INNER JOIN .t_message_mng AS mm ON mm.message_id = mrc.message_id AND ((mm.span_flg =1 AND mm.span_from <= '01/07/2019 00:00:00.000' AND mm.span_to >= '01/07/2019 00:00:00.000') OR mm.span_Flg = 0) INNER JOIN tenant_post_detail_list tpl ON (tpl.post_id = mm.post_id AND tpl.post_id <> 'tenant' AND tpl.user_id = ' ') OR (tpl.user_id = mm.user_id AND tpl.post_id = 'tenant' AND tpl.user_id <> ' ') LEFT OUTER JOIN .t_message_reply AS mrp ON mrp.message_receive_id = mrc.message_receive_id AND mrp.reply_no = 1 LEFT OUTER JOIN (SELECT max(reply_message_id) AS max_reply_message_id FROM .t_message_mng WHERE reply_message_id IS NOT NULL AND post_id = '20100' GROUP BY reply_message_id ) AS reply_check ON reply_check.max_reply_message_id = mm.message_id WHERE mrc.del_flg = 0 AND mrc.post_id = '20100' AND mm.message_name LIKE '%%' ESCAPE '~' AND mm.note LIKE '%%' ESCAPE '~' ORDER BY mrc.reg_time DESC ,mm.message_id ASC

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2019/01/07 07:12

>コストは1以上あってはならないという認識 この根拠は何でしょうか?記事など出展を質問本文に追記してください
m.ts10806

2019/01/07 07:20

質問本文に追記してください(質問は編集できます) ただ、目的が「コストの改善」なのであれば実行計画の結果だけではある程度原因は追えても具体的な解決策の提案とはならないように思いますが、この質問のゴールはどこに置いていますか?その辺りも含めて質問に追記してください
退会済みユーザー

退会済みユーザー

2019/01/07 07:48 編集

Nested Loop ではじめからコストが高いやつが、なんの式を書いているか次第のような気がするので、結局SQL文を見ないとはじまらない。
Orlofsky

2019/01/07 09:05

https://teratail.com/questions/167224 のように使っているテーブルの CREATE TABLE, CREATE INDEX と INSERT で提示するとSQLをすぐ実行できるので、適切なコメントが付き易いかと。
退会済みユーザー

退会済みユーザー

2019/02/15 11:54

有識者にお金を払ってチューニングしてもらったほうがいいと思いますが無料でしたいなら 関連する DDL を全部出してもらわないと根本敵な解決にはならないとおもいます。 というか、これここで出しちゃだめなSQLにみえる(先方(問い合わせ先)があるってことは守秘義務違反は大丈夫なのか)
guest

回答2

0

(コストは1以上あってはならないという認識があります)

関係ないですね。

値のシーケンシャルI/Oで1ページを読みこむコストを1とした際の相対値で示される

これはコスト値の値がどのようなものかの説明です。

確認するべきは、actual timeです。

チューニングが必要なのは以下の部分です。

-> Nested Loop (cost=133.11..5279.44 rows=17 width=1386) (actual time=27.226..3547.307 rows=3790 loops=1) -> Nested Loop (cost=132.68..4502.88 rows=115 width=1370) (actual time=27.183..3164.768 rows=24443 loops=1) -> CTE Scan on tenant_post_detail_list tpl (cost=0.00..0.42 rows=1 width=102) (actual time=12.285..256.152 rows=677 loops=1)

CTE Scan部分は244ms掛かっていますので、可能ならチューニングですけど、それよりも、それとの突合でNested Loop部分が3000msと500ms位になってますので、ここを何とかしないと改善されません。

追記

※SQLが追加されたので。
一番コストが掛かっているのは以下ですね。

SQL

1 INNER JOIN tenant_post_detail_list tpl 2 ON 3 (tpl.post_id = mm.post_id 4 AND tpl.post_id <> 'tenant' 5 AND tpl.user_id = ' ') 6 OR 7 (tpl.user_id = mm.user_id 8 AND tpl.post_id = 'tenant' 9 AND tpl.user_id <> ' ')

ここは先ず、t_message_mngにpost_idとuser_idのインデックスが必要ですね。
先ずは両方の項目を持ったインデックスを適用して、改善されないなら、別々のインデックスにして、or結合じゃなく、left join で各々結合する。

追記

以下は問題部分だけのSQLです。
先ずはこれで検証してみましょう。

SQL

1WITH tenant_post_detail_list AS (select) 2SELECT 3 mm.* 4 ,tpl.tenant_post_name 5 FROM 6 .t_message_mng AS mm 7 INNER JOIN tenant_post_detail_list tpl 8 ON 9 (tpl.post_id = mm.post_id 10 AND tpl.post_id <> 'tenant' 11 AND tpl.user_id = ' ') 12 OR 13 (tpl.user_id = mm.user_id 14 AND tpl.post_id = 'tenant' 15 AND tpl.user_id <> ' ')

上記を計測したのち、以下の組み替えたものを実行してみてください。

SQL

1WITH tenant_post_detail_list AS (select) 2SELECT 3 mm.* 4 , case when tpl1.post_id <> 'tenant' AND tpl1.user_id = ' ' then tpl1.tenant_post_name 5 else case when tpl2.post_id = 'tenant' AND tpl2.user_id <> ' ' then tpl2.tenant_post_name 6 end end tenant_post_name 7 FROM 8 .t_message_mng AS mm 9 left JOIN tenant_post_detail_list tpl1 10 ON .post_id = mm.post_id or 11 left JOIN tenant_post_detail_list tpl2 12 ON tpl2.user_id = mm.user_id

さらに、t_message_mngにpost_idとuser_idのインデックスを追加し計測してみて下さい。
効果が無ければ、インデックスが適用されるようにSQLを見直しです。

投稿2019/01/07 07:36

編集2019/01/07 09:54
sazi

総合スコア25184

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/01/07 07:48

そこのNested Loopがすっごく気になる。
riomakopa

2019/01/07 08:07

sazi様 m6u様 お二人にご教授頂いたことを整理してやってみます。 ありがとうございます。 また、お聞きするかもしれませんがよろしくお願いいたします。
riomakopa

2019/01/07 09:32 編集

m6u様 mm.span_from と mm.span_toはdate型でした。 sazi様 お恥ずかしいですが、別々のINDEXにするということは以下であってますでしょうか? ‘‘‘ INNER JOIN t_message_mng AS mm ON mm.message_id = mrc.message_id AND ((mm.span_flg =1 AND mm.span_from <= '01/07/2019 00:00:00.000' AND mm.span_to >= '01/07/2019 00:00:00.000') OR mm.span_Flg = 0) LEFT JOIN tenant_post_detail_list tpl ON (tpl.post_id = mm.post_id AND tpl.post_id <> 'tenant' AND tpl.user_id = ' ') ‘‘‘ ↑に変更したことで体感です4秒かかっていたのが 0.5秒に短縮できたのですが・・自身がないです・・
sazi

2019/01/08 04:10 編集

別々のINDEXにするというのは、問い合わせを別にするという事になります。 (SQLを分けるのでななくサブクエリーにするということ) t_message_mng とtenant_post_detail_list をそれぞれの条件で結合したものをunionすることになります。
sazi

2019/01/07 10:17

追記したので、確認してみて下さい。
guest

0

テーブルの構造も教えてほしいところだけど、
以下、気になったところ。

mm.span_from と mm.span_to ってtimestamp型だろうか、
もし文字列での比較をやっているならtimestamp型を駆使できるように構造を見直すべきで
インデックスが利いてなかったりしないかな。

tpl.user_id や tpl.post_id が文字列っぽいけどvarcharなのかそれともtextなのか。
使用する文字列のパターンが数限られるのであればいっそ整数値にしてしまって、
インデックスを利かせやすくしたほうが良いのではないかと。

REINDEX文の活用も。

-> Nested Loop (cost=268.15..1182.94 rows=1 width=52) (actual time=11.120..35.718 rows=664 loops=1)

664行ひねりだすのに実実行時間で11~35ミリ秒35引く11で24ミリ秒もかかってるここが気になる。
インデックス使えてないんだろうね。

投稿2019/01/07 07:47

編集2019/01/07 08:44
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sazi

2019/01/07 08:03 編集

時間の単位はミリ秒(ms)ですよ。 また、actual timeは、開始...終了で表現されるので、差分が実行に掛かった時間です。 なので、指摘部分は、35-11=24msです。
退会済みユーザー

退会済みユーザー

2019/01/07 08:39

こっ恥ずかしいミスのご指摘感謝します。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問