主キー制約を作成すると、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 |
------------------------------------------------------