2

おはようございます、

昨日、適切なサイズの 2 つの結果セット (それぞれの結果が 50k 未満) の間で作成したクエリでは、JOIN の一部は、データが一致するか null であるかをチェックする句でした (以下の簡易バージョン)。

SELECT a JOIN b ON a.class = b.class OR (a.class is null AND b.class is null)

ただし、OR ステートメントの使用を中心とした深刻なパフォーマンスの問題に気付きました。以下を使用して問題を回避しました。

SELECT a JOIN b ON NVL(a.class, 'N/A') = NVL(b.class, 'N/A')

最初のクエリの実行時間は許容できないほど長く、2 番目のクエリは数桁高速です (>45 分対 <1)。比較が増えるため、OR の実行が遅くなると予想されますが、この特定のデータセットでは、a.class = b.class = null のケースは比較的少数です。

パフォーマンス時間がこのように劇的に増加する原因は何ですか? Oracle SQL は、他の多くの言語のようにブール比較を短絡しませんか? 最初のクエリを 2 番目のクエリでサルベージする方法はありますか (Oracle だけでなく一般的な SQL で使用するため)。

4

5 に答える 5

4

null クラスを持つ任意のレコードでクロス積を返しています。あなたの結果はこれでよろしいですか?

11gR2 で 2 つのサンプル クエリを作成しました。

WITH a as 
(select NULL as class, 5 as columna from dual
 UNION
 select NULL as class, 7 as columna from dual
 UNION
 select NULL as class, 9 as columna from dual
 UNION
 select 'X' as class, 3 as columna from dual
 UNION
 select 'Y' as class, 2 as columna from dual),
 b as 
 (select NULL as class, 2 as columnb from dual
 UNION
 select NULL as class, 15 as columnb from dual
 UNION
 select NULL as class, 5 as columnb from dual
 UNION
 select 'X' as class, 7 as columnb from dual
 UNION
 select 'Y' as class, 9 as columnb from dual)
    SELECT * from a JOIN b ON (a.class = b.class 
                              OR (a.class is null AND b.class is null))

このクエリで EXPLAIN PLAN を実行すると、テーブル (私の場合はインライン ビュー) が NESTED LOOPS を介して結合されていることが示されます。NESTED LOOPS 結合は、一方のテーブルの最初の行をスキャンし、次に他方のテーブルの各行をスキャンして一致するかどうかを調べ、次に最初のテーブルの 2 行目をスキャンし、2 番目のテーブルで一致を探す、というように動作します。直接比較していないためです。 JOIN の OR 部分にいずれかのテーブルがある場合、オプティマイザーは NESTED LOOPS を使用する必要があります。

舞台裏では次のようになります。

  • テーブル A の行 1 を取得します。クラスが null の場合、テーブル A のこの行を結果セットに含めます。
  • テーブル A の行 1 にいる間に、クラスが null であるすべての行をテーブル B で検索します。
  • テーブル A の行 1 とテーブル B のすべての行でクロス積を実行します。
  • これらの行を結果セットに含めます
  • テーブル A の行 2 を取得します。クラスが null の場合、テーブル A のこの行を結果セットに含めます。
  • ....など

SELECT ステートメントを に変更するとSELECT * FROM a JOIN b ON NVL(a.class, 'N/A') = NVL(b.class, 'N/A')、EXPLAIN は HASH JOIN が使用されていることを示します。ハッシュ結合は、基本的に小さいテーブルの各結合キーのハッシュを生成し、次に大きいテーブルをスキャンして、一致する各行の小さいテーブルのハッシュを見つけます。この場合、これは単純な Equijoin であるため、オプティマイザーは駆動テーブルの各行を問題なくハッシュできます。

舞台裏では次のようになります。

  • テーブル A を調べて、NULL クラス値を「N/A」に変換します
  • テーブル A の各行をハッシュ化します。
  • ハッシュ テーブル A は現在、一時領域またはメモリにあります。
  • テーブル B をスキャンし、NULL クラス値を「N/A」に変換してから、値のハッシュを計算します。ハッシュ テーブルのルックアップ ハッシュが存在する場合は、結果セットにテーブル A および B から結合された行を含めます。
  • B のスキャンを続行します。

クエリに対して EXPLAIN PLAN を実行すると、おそらく同様の結果が得られます。

最終結果は同じですが、最初のクエリで「OR」を使用してテーブルを結合していないため、オプティマイザーはより良い結合方法を使用できません。駆動テーブルが大きい場合、または大きなセカンダリ テーブルに対してフル テーブル スキャンを強制している場合、NESTED LOOPS は非常に遅くなる可能性があります。

ANSI 関数を使用してCOALESCE、他のデータベース システムで NVL oracle 関数をエミュレートできます。ここでの本当の問題は、NULL 値に参加しようとしているということです。実際には、「NO CLASS」または null = unknown の代わりに null = nothing という意味で「null」クラスを識別する他の方法が必要です。 .

コメントであなたの質問に答える補遺:

null クエリの場合、SQL エンジンは次のことを行います。

  1. テーブル A から行 1 を読み取ります。クラスは null です。「N/A」に変換します。
  2. テーブル B には、クラスが null である 3 つの行があり、各 null を「N/A」に変換します。
  3. 最初の行は 3 行すべてに一致するため、A1B1、A1B2、A1B3 の 1 行の 3 行が結果セットに追加されます。
  4. テーブル A から行 2 を読み取ります。クラスは null です。「N/A」に変換します。
  5. テーブル B には、クラスが null である 3 つの行があり、各 null を「N/A」に変換します。
  6. 2 行目は 3 行すべてに一致するため、A2B1、A2B2、A2B3 の 1 行である 3 行が結果セットに追加されます。
  7. テーブル A から行 3 を読み取ります。クラスは null です。「N/A」に変換します。
  8. テーブル B には、クラスが null である 3 つの行があり、各 null を「N/A」に変換します。
  9. 3 行目は 3 行すべてに一致するため、A3B1、A3B2、A3B3 の 1 行の 3 行が結果セットに追加されます。10. 行 4 と 5 は null ではないため、結合のこの部分では処理されません。

「N/A」クエリの場合、SQL エンジンは次のことを行います。

  1. テーブル A から行 1 を読み取ります。クラスは null です。「N/A」に変換し、この値をハッシュします。
  2. テーブル A から行 2 を読み取ります。クラスは null です。「N/A」に変換し、この値をハッシュします。
  3. テーブル A から行 3 を読み取ります。クラスは null です。「N/A」に変換し、この値をハッシュします。
  4. テーブル A から行 4 を読み取り、クラスは null ではなく、この値をハッシュします。
  5. テーブル A から行 5 を読み取り、クラスは null ではなく、この値をハッシュします。
  6. ハッシュ テーブル C がメモリに格納されます。
  7. テーブル B から行 1 を読み取り、クラスは null であり、「N/A」に変換し、値をハッシュします。
  8. ハッシュ値をメモリ内のハッシュ テーブルと比較し、一致ごとに結果セットに行を追加します。A1、A2、A3 の 3 つの行が見つかりました。結果は、A1B1、A2B1、A3B1 に追加されます。
  9. テーブル B から行 2 を読み取り、クラスは null であり、「N/A」に変換し、値をハッシュします。
  10. ハッシュ値をメモリ内のハッシュ テーブルと比較し、一致ごとに結果セットに行を追加します。A1、A2、A3 の 3 つの行が見つかりました。結果は、A1B2、A2B2、A3B2 に追加されます。
  11. テーブル B から行 3 を読み取ります。クラスは null です。「N/A」に変換し、値をハッシュします。
  12. ハッシュ値をメモリ内のハッシュ テーブルと比較し、一致ごとに結果セットに行を追加します。A1、A2、A3 の 3 つの行が見つかりました。結果は、A1B3、A2B3、A3B3 に追加されます。
于 2012-05-04T17:16:13.860 に答える
1

最初のケースでは、各nullが異なるため、データベースは最適化を使用しません(aテーブルの各行をチェックするからのすべての行に対してb)。

2番目のケースでは、データベースは最初にすべてのnullを「N / A」に変更し、次に最適化を使用してa.classとのみを比較しますb.class

Oracleでnullを比較するのは非常に時間がかかります。ヌルは未定義の値です-1つのヌルは他のヌルとは異なります。2つのほぼ同一のクエリの結果を比較します。

select 1 from dual where null is null

select 1 from dual where null = null

特別な句を含む最初のクエリのみがis null正解を返します。したがって、null値にインデックスを付けることはできません。

于 2012-05-04T14:51:32.867 に答える
-1

これを試してください:

SELECT a from Table1 a JOIN JTable1 b ON a.class = b.class
where a.class is null
union all
SELECT a from Table1 a JOIN JTable1 b ON a.class = b.class
where b.class is null

マグナチュードをより速くする必要があります

于 2012-05-04T14:56:23.793 に答える
-1

説明は簡単です。まず、結合操作でネストされたループを使用する必要があります。これは、OR 操作を使用すると常に発生します。2 つ目はハッシュ結合操作を使用する必要があり、前のものよりも高速です。

于 2013-10-12T02:59:19.727 に答える
-2

もう少し簡単にしてみませんか。お気に入り

SELECT * FROM a,b WHERE a.class(+)=b.class(+)

より読みやすいと思います。

于 2012-05-04T15:07:10.743 に答える