2

サイズが非常に大きい4〜5つのテーブルがあり、以下のクエリを使用して外部結合されたままになっています。パフォーマンスを改善できるように書き直す方法はありますか?

SELECT t1.id,
     MIN(t5.date) AS first_pri_date,
     MIN(t3.date) AS first_pub_date,
     MAX(t3.date) AS last_publ_date,
     MIN(t2.date) AS first_exp_date
FROM t1
    LEFT JOIN t2 ON (t1.id = t2.id)
    LEFT JOIN t3 ON (t3.id = t1.id)
    LEFT JOIN t4 ON (t1.id = t4.id)
    LEFT JOIN t5 ON (t5.p_id =t4.p_id)
GROUP BY t1.id
ORDER BY t1.id;

レコード数は次のとおりです。

  • t1: 6434323
  • t2: 6934562
  • t3: 9141420
  • t4: 11515192
  • t5: 3797768

結合に使用されるほとんどの列にインデックスがあります。説明計画で最も消費される部分はt4、最終的に発生する外部結合です。これを書き直してパフォーマンスを向上させる方法があるかどうか知りたかっただけです。

4

2 に答える 2

1

idが の主キーであると仮定すると、次のように記述したt1場合、クエリはより適切に実行される可能性があります (または、Oracle の PGA の設定に依存します)。

SELECT --+ leading(t1) use_hash(t2x,t3x,t45x) full(t1) no_push_pred(t2x) no_push_pred(t3x) no_push_pred(t45x) all_rows
    t1.id,
    t45x.first_pri_date,
    t3.first_pub_date,
    t3.last_publ_date,
    t2.first_exp_date
FROM t1
    LEFT JOIN (
        SELECT t2.id,
            MIN(t2.date) AS first_exp_date
        FROM t2
        GROUP BY t2.id
    ) t2x
        ON t2x.id = t1.id
    LEFT JOIN (
        SELECT t3.id,
            MIN(t3.date) AS first_pub_date,
            MAX(t3.date) AS last_publ_date
        FROM t3
        GROUP BY t3.id
    ) t3x
        ON t3x.id = t1.id
    LEFT JOIN (
        SELECT --+ leading(t5) use_hash(t4)
            t4.id,
            MIN(t5.date) AS first_pri_date
        FROM t4
            JOIN t5 ON t5.p_id = t4.p_id
        GROUP BY t4.id
    ) t45x
        ON t45x.id = t1.id
ORDER BY t1.id;

この書き直しは、追加のインデックスを作成する必要を課すものではありませんが、そうでなければ役に立たないものです。

于 2014-11-02T20:48:55.783 に答える
1

あなたの問題は、多くの LEFT JOIN を実行していて、それらすべての JOIN を適用した後に最終的な結果セットが大きくなりすぎることだと思います。また、可能な限り高速な方法で MIN または MAX を計算するために、この方法でインデックスを使用することはできません。インデックスをうまく使用すると、MIN または MAX を非常に迅速に計算できるはずです。

クエリは次のように記述します。

SELECT t1.id,     
(SELECT MIN(t5.date) FROM t5 JOIN t4 ON t5.p_id = t4.p_id WHERE t4.id = t1.id) AS first_pri_date,
(SELECT MIN(date) FROM t3 WHERE t3.id = t1.id) AS first_pub_date,
(SELECT MAX(date) FROM t3 WHERE t3.id = t1.id)  AS last_publ_date,
(SELECT MIN(date) FROM t2 WHERE t2.id = t1.id) AS first_exp_date
FROM t1
ORDER BY t1.id;

パフォーマンスを向上させるには、(id, date)またはにインデックスを作成します(p_id, date)。したがって、インデックスは次のようになります。

CREATE INDEX ix2 ON T2 (id,date);
CREATE INDEX ix3 ON T3 (id,date);
CREATE INDEX ix5 ON T5 (p_id,date);
CREATE INDEX ix4 ON T4 (id);

t4しかし、との間の結合にはまだ問題が残っていt5ます。と が 1 対 1 の関係にある場合t1t42 行目に次のように書くとさらによいでしょう。

(SELECT MIN(t5.date) FROM t5 WHERE t5.p_id = (SELECT p_id FROM t4 WHERE t4.id=t1.id)) AS first_pri_date,

1:N で、CROSS APPLY と OUTER APPLY が Oracle バージョンで動作する場合は、2 行目を次のように書き換えることができます。

 (SELECT MIN(t5min.PartialMinimum) 
 FROM t4 
 CROSS APPLY 
 (
    SELECT PartialMinimum = MIN(t5.date)
    FROM t5
    WHERE t5.p_id = t4.p_id
 ) AS t5min
 WHERE t4.id = t1.id) 
 AS first_pri_date

これはすべて、MIN または MAX の計算中にインデックスを最大限に活用することを目的としています。したがって、SELECT 全体は次のように書き直すことができます。

SELECT t1.id,     
 (SELECT MIN(t5min.PartialMinimum) 
 FROM t4 
 CROSS APPLY 
 (
    SELECT TOP 1 PartialMinimum = date
    FROM t5
    WHERE t5.p_id = t4.p_id
    ORDER BY 1 ASC
 ) AS t5min
 WHERE t4.id = t1.id)  AS first_pri_date,
(SELECT TOP 1 date FROM t2 WHERE t2.id = t1.id ORDER BY 1 ASC)  AS first_exp_date,
(SELECT TOP 1 date FROM t3 WHERE t3.id = t1.id ORDER BY 1 ASC)  AS first_pub_date,
(SELECT TOP 1 date FROM t3 WHERE t3.id = t1.id ORDER BY 1 DESC)  AS last_publ_date
FROM t1 
ORDER BY 1;

これは、履歴データ テーブルから MIN または MAX を取得する最も最適な方法であると私が信じているとおりです。

ポイントは、MIN を多くのインデックスなしの値で使用すると、サーバーがすべてのデータをメモリにロードし、インデックスなしのデータから MIN または MAX を計算することです。これには、I/O 操作の要求が高いため、時間がかかります。 . MIN または MAX を使用するときにインデックスを不適切に使用すると、すべての履歴テーブル データがメモリにキャッシュされ、MIN または MAX の計算以外には必要ないという状況につながる可能性があります。

クエリの CROSS APPLY 部分がなければ、サーバーは t5 からすべての個々の日付をメモリにロードし、ロードされた結果セット全体から MAX を計算する必要があります。

適切にインデックス付けされたテーブルの MIN 関数は、非常に高速な TOP 1 ORDER BY のように動作することに注意してください。このようにして、すぐに結果を得ることができます。

CROSS APPLY は Oracle 12C で使用できます。それ以外の場合は、パイプライン関数を使用できます。

このSQL Fiddle、特に実行計画の違いを確認してください。

于 2014-11-02T21:23:59.487 に答える