3800以上の行を持つテーブルAと、300以上の行を持つ別のテーブルBがあります。
このような更新を実行すると:
update A set some_colum = '2' where another_column not in (select id from B)
完了するまで33秒かかります。それはあまりにも悪いパフォーマンスですか?
改善方法?
A.another_columnに一意でないインデックスを追加しましたが、役に立ちません。 B.idは主キーです。
ここに説明があります:
私の短い答えはみんなの好みではなかったようですので、長い答えをお見せしましょう
まず、ラボを作成しましょう。
@WestFarmerはいくつかのテーブルに言及しました
SQL>create table A (
2 some_column number,
3 another_column number
4 );
Table created.
SQL>create table B (
2 id number
3 );
Table created.
また、彼はテーブルA(another_column)にインデックスを作成し、B(id)が主キーであると述べました。
SQL>create index idx1 on A (another_column);
Index created.
SQL>alter table B add constraint b_pk primary key (id);
Table altered.
次に、両方のテーブルにデータを入力します。Aの場合は3800 +、Bの場合は300+です。4000と400に修正します。
SQL>declare
2 i number;
3 j number;
4 begin
5 for i in 1..400 loop
6 for j in 1..10 loop
7 insert into A values (j, i+100);
8 end loop;
9 insert into B values (i);
10 end loop;
11 end;
12 /
PL/SQL procedure successfully completed.
また、私はこの追加のレコードを追加します。理由は、存在しないのではなく存在しないことを提案する理由を示すためです。
SQL>insert into A values (1,NULL);
1 row created.
SQL>commit;
Commit complete.
以下に示すように、ANOTHER_COLUMNには、テーブルAの101〜500の値が含まれます。
SQL>select max(another_column), min(another_column) from A
MAX(ANOTHER_COLUMN) MIN(ANOTHER_COLUMN)
------------------- -------------------
500 101
そしてidは1から400までの値を格納します
SQL>select max(id), min(id) from B;
MAX(ID) MIN(ID)
---------- ----------
400 1
ここで、ロジックはNOT INはNOT EXISTと同じである必要があると述べましたが、not inを使用してレコードをカウントすると、NULL値が含まれていないことがわかります。
SQL>select count(*) from A where another_column not in (select id from B);
COUNT(*)
----------
1000
ただし、存在しない場合は、挿入したnullレコードが含まれます。
SQL>select count(*) from A where not exists (select null from B where another_column = id);
COUNT(*)
----------
1001
ここでの質問は、NULL値については何も言及せず、パフォーマンスについてです。では、nullレコードを削除しましょう
SQL>delete A where another_column is null;
1 row deleted.
SQL>commit;
Commit complete.
SQL>select count(*) from A where another_column not in (select id from B);
COUNT(*)
----------
1000
SQL>select count(*) from A where not exists (select null from B where another_column = id);
COUNT(*)
----------
1000
今、両方のオプションは似ていますよね? NOT INまたはNOT EXISTを使用しているかどうかは関係ありません。
パフォーマンスの観点から、より良いオプションが存在しない理由を示します
実行計画を表示するために、自動トレースオプションをアクティブにしました。
まず、NOT INオプションを実行しましょう
SQL>update A set some_column = '2' where another_column not in (select id from B);
1000 rows updated.
Elapsed: 00:00:00.39
Execution Plan
----------------------------------------------------------
Plan hash value: 3794573283
------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 4000 | 152K| 4 |
| 1 | UPDATE | A | | | |
|* 2 | HASH JOIN RIGHT ANTI SNA| | 4000 | 152K| 4 |
| 3 | INDEX FULL SCAN | B_PK | 400 | 5200 | 1 |
| 4 | TABLE ACCESS FULL | A | 4000 | 101K| 2 |
------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ANOTHER_COLUMN"="ID")
Note
-----
- cpu costing is off (consider enabling it)
- dynamic sampling used for this statement (level=2)
Statistics
----------------------------------------------------------
7 recursive calls
1022 db block gets
44 consistent gets
0 physical reads
248832 redo size
397 bytes sent via SQL*Net to client
584 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1000 rows processed
では、NOT EXISTオプションを実行しましょう
SQL>update A set some_column = '2' where not exists (select null from B where another_column = id);
1000 rows updated.
Elapsed: 00:00:00.33
Execution Plan
----------------------------------------------------------
Plan hash value: 2312933728
------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 4000 | 152K| 3 |
| 1 | UPDATE | A | | | |
| 2 | NESTED LOOPS ANTI | | 4000 | 152K| 3 |
| 3 | TABLE ACCESS FULL| A | 4000 | 101K| 2 |
|* 4 | INDEX UNIQUE SCAN| B_PK | 1 | 13 | 1 |
------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("ANOTHER_COLUMN"="ID")
Note
-----
- cpu costing is off (consider enabling it)
- dynamic sampling used for this statement (level=2)
Statistics
----------------------------------------------------------
7 recursive calls
15 db block gets
47 consistent gets
0 physical reads
94068 redo size
394 bytes sent via SQL*Net to client
601 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1000 rows processed
経過時間を確認すると非常によく似ていますが、実行プランを確認すると、小さな違いが1つあります。
使用されていないハッシュ結合は存在しませんが、存在しない場合はネストされたループを使用します。この場合、このクエリを実行するためのより良い方法です。実際、コストは4と3で異なります。大きな違い!
注:A(another_column)のインデックスは、どのオプションを使用しても役に立ちません。