web-dev-qa-db-ja.com

Oracleでテーブルの更新に時間がかかる理由

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は主キーです。

ここに説明があります: enter image description here

3
WestFarmer

私の短い答えはみんなの好みではなかったようですので、長い答えをお見せしましょう

まず、ラボを作成しましょう。

@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)のインデックスは、どのオプションを使用しても役に立ちません。

3