6

次のユースケースがあります。

テーブルには、変更されたデータと元のデータが格納されます。私のクエリは、各人に対して 1 つの行のみを取得するように設計されています。変更されたデータがある場合は変更されたデータ、そうでない場合は元のデータです。

テーブルに 10 万行のデータと 2k の変更済みデータを入力しました。テーブルで主キーを使用すると、クエリは 0.5 秒未満で実行されます。主キーの代わりにテーブルにインデックスを配置すると、クエリの実行が非常に遅くなります。したがって、間違いなく主キーを使用します。

私の質問は次のとおりです: PK アプローチは、インデックスを使用するアプローチよりもはるかに高速なのはなぜですか?

ここにコード:

drop table up_data cascade constraints purge;
/
create table up_data(
 pk integer,
 hp_nr integer,
 up_nr integer,
 ps_flag varchar2(1),
 ps_name varchar2(100)
 -- comment this out and uncomment the index below.
, constraint pk_up_data primary key (pk,up_nr) 
);
/
-- insert some data
insert into up_data
select rownum, 1, 0, 'A', 'tester_' || to_char(rownum) 
from dual 
connect by rownum < 100000;
/
-- insert some changed data
-- change ps_flag = 'B' and mark it with a change number in up_nr
insert into up_data
select rownum, 1, 1, 'B', 'tester_' || to_char(rownum) 
from dual 
connect by rownum < 2000;
/
-- alternative(?) to the primary key
-- CREATE INDEX idx_up_data ON up_data(pk, up_nr);
/

select ステートメントは次のようになります。

select count(*)
from
(
  select *
  from up_data u1 
  where up_nr = 1 
  or (up_nr = 0 
      and pk not in (select pk from up_data where up_nr = 1)
      )
) u

ステートメントは最適化の対象になる可能性がありますが、当面はこのままです。

4

1 に答える 1

8

主キー制約を作成すると、Oracle は同時にこれをサポートするインデックスも作成します。主キー インデックスには、基本インデックスとの重要な違いがいくつかあります。

  • この値はすべて一意であることが保証されています
  • (PK を形成する列の) 表の行に null はありません。

これらの理由は、表示されるパフォーマンスの違いの鍵です。セットアップを使用して、次のクエリ プランを取得します。

--fast version with PK
explain plan for 
select count(*)
from
(
  select *
  from up_data u1 
  where up_nr = 1 
  or (up_nr = 0 
      and pk not in (select pk from up_data where up_nr = 1)
      )
) u
/
select * from table(dbms_xplan.display(NULL, NULL,'BASIC +ROWS'));

-----------------------------------------------------                                                                                                                                                                                                                                                        
| Id  | Operation              | Name       | Rows  |                                                                                                                                                                                                                                                        
-----------------------------------------------------                                                                                                                                                                                                                                                        
|   0 | SELECT STATEMENT       |            |     1 |                                                                                                                                                                                                                                                        
|   1 |  SORT AGGREGATE        |            |     1 |                                                                                                                                                                                                                                                        
|   2 |   FILTER               |            |       |                                                                                                                                                                                                                                                        
|   3 |    INDEX FAST FULL SCAN| PK_UP_DATA |   103K|                                                                                                                                                                                                                                                        
|   4 |    INDEX UNIQUE SCAN   | PK_UP_DATA |     1 |                                                                                                                                                                                                                                                        
-----------------------------------------------------     

alter table up_data drop constraint pk_up_data;
CREATE INDEX idx_up_data ON up_data(pk, up_nr);
/

--slow version with normal index
explain plan for
select count(*)
from
(
  select *
  from up_data u1 
  where up_nr = 1 
  or (up_nr = 0 
      and pk not in (select pk from up_data where up_nr = 1)
      )
) u
/

select * from table(dbms_xplan.display(NULL, NULL,'BASIC +ROWS'));

------------------------------------------------------                                                                                                                                                                                                                                                       
| Id  | Operation              | Name        | Rows  |                                                                                                                                                                                                                                                       
------------------------------------------------------                                                                                                                                                                                                                                                       
|   0 | SELECT STATEMENT       |             |     1 |                                                                                                                                                                                                                                                       
|   1 |  SORT AGGREGATE        |             |     1 |                                                                                                                                                                                                                                                       
|   2 |   FILTER               |             |       |                                                                                                                                                                                                                                                       
|   3 |    INDEX FAST FULL SCAN| IDX_UP_DATA |   103K|                                                                                                                                                                                                                                                       
|   4 |    INDEX FAST FULL SCAN| IDX_UP_DATA |  1870 |                                                                                                                                                                                                                                                       
------------------------------------------------------ 

大きな違いは、高速バージョンでは、テーブル データの 2 回目のアクセスで INDEX FAST FULL SCAN ではなく、INDEX UNIQUE SCAN が使用されることです。

Oracleドキュメントから(強調鉱山):

インデックス レンジ スキャンとは対照的に、インデックス ユニーク スキャンでは、インデックス キーに関連付けられた 0 または 1 つの行 ID が必要です。述語が等値演算子を使用して UNIQUE インデックス キー内のすべての列を参照する場合、データベースはユニーク スキャンを実行します。インデックス ユニーク スキャン は、最初のレコードが見つかるとすぐに処理を停止します。これは、 2 番目のレコードが存在しないためです。

処理を停止するこの最適化は、この例の重要な要因であることがわかります。クエリの高速バージョン:

  • フル スキャン ~ 103,000 のインデックス エントリ
  • これらのそれぞれについて、PK インデックスで一致する行を 1 つ見つけ、2 番目のインデックスの処理を停止します。

遅いバージョン:

  • フル スキャン ~ 103,000 のインデックス エントリ
  • これらのそれぞれについて、103,000 行の別のスキャンを実行して、一致するものがあるかどうかを確認します。

したがって、行われた作業を比較するには:

  • PK を使用すると、高速なフル スキャンが 1 回実行され、次に 1 つのインデックス値の 103,000 回のルックアップが実行されます。
  • 通常のインデックスでは、高速なフル スキャンを 1 回行ってから、103,000 のインデックス エントリを 103,000 回スキャンします。

この例では、パフォーマンス上の利点を得るために、主キーの一意性とインデックス値の非 null 性の両方が必要です。

-- create index as unique - we still get two fast full scans
drop index index idx_up_data;
create unique index idx_up_data ON up_data(pk, up_nr);

explain plan for 
select count(*)
from
(
  select *
  from up_data u1 
  where up_nr = 1 
  or (up_nr = 0 
      and pk not in (select pk from up_data where up_nr = 1)
      )
) u
/
select * from table(dbms_xplan.display(NULL, NULL,'BASIC +ROWS'));

------------------------------------------------------                                                                                                                                                                                                                                                       
| Id  | Operation              | Name        | Rows  |                                                                                                                                                                                                                                                       
------------------------------------------------------                                                                                                                                                                                                                                                       
|   0 | SELECT STATEMENT       |             |     1 |                                                                                                                                                                                                                                                       
|   1 |  SORT AGGREGATE        |             |     1 |                                                                                                                                                                                                                                                       
|   2 |   FILTER               |             |       |                                                                                                                                                                                                                                                       
|   3 |    INDEX FAST FULL SCAN| IDX_UP_DATA |   103K|                                                                                                                                                                                                                                                       
|   4 |    INDEX FAST FULL SCAN| IDX_UP_DATA |  1870 |                                                                                                                                                                                                                                                       
------------------------------------------------------     

-- now the columns are not null, we see the index unique scan
alter table up_data modify (pk not null, up_nr not null);

explain plan for
select count(*)
from
(
  select *
  from up_data u1 
  where up_nr = 1 
  or (up_nr = 0 
      and pk not in (select pk from up_data where up_nr = 1)
      )
) u
/

select * from table(dbms_xplan.display(NULL, NULL,'BASIC +ROWS'));

------------------------------------------------------                                                                                                                                                                                                                                                       
| Id  | Operation              | Name        | Rows  |                                                                                                                                                                                                                                                       
------------------------------------------------------                                                                                                                                                                                                                                                       
|   0 | SELECT STATEMENT       |             |     1 |                                                                                                                                                                                                                                                       
|   1 |  SORT AGGREGATE        |             |     1 |                                                                                                                                                                                                                                                       
|   2 |   FILTER               |             |       |                                                                                                                                                                                                                                                       
|   3 |    INDEX FAST FULL SCAN| IDX_UP_DATA |   103K|                                                                                                                                                                                                                                                       
|   4 |    INDEX UNIQUE SCAN   | IDX_UP_DATA |     1 |                                                                                                                                                                                                                                                       
------------------------------------------------------  
于 2013-06-16T15:44:32.653 に答える