« Stored Procedure で host command #3 | トップページ | Oracle Database 11g »

2007年7月11日 (水) / Author : Hiroshi Sekiguchi.

Virtual Index

個人的にはOracle9iのころも含めて実践では利用した事は無い機能なのだが、Oracle ACEの一人である、Chris Foot氏が、dbazineでVirtual Indexについて書いていたので試してみることにした。


注)仮想索引は、今のところマニュアルに記載されていない隠し機能の一つなので、利用する際は、your own risk! ですよ。
  

まずは、準備から。dbazine.comの記事でも利用されている、HRスキーマ(サンプルスキーマ)を利用し試してみた。

SYS> 
SYS> select count(*) from hr.departments;

COUNT(*)
----------
27

SYS> select count(*) from hr.employees;

COUNT(*)
----------
107

SYS> create table scott.departments as select * from hr.departments;

表が作成されました。

SYS> create table scott.employees as select * from hr.employees;

表が作成されました。

SYS> conn scott/tiger
接続されました。


SCOTT> insert into employees select * from employees;

107行が作成されました。


....中略....


SCOTT> r
1* insert into employees select * from employees

109568行が作成されました。

SCOTT> commit;

コミットが完了しました。

SCOTT>

SCOTT> alter table departments add constraint pk_departments primary key (department_id);

表が変更されました。

SCOTT> exec dbms_stats.gather_schema_stats(ownname=>'SCOTT',cascade=>true);

PL/SQLプロシージャが正常に完了しました。


SCOTT> alter session set optimizer_mode = 'FIRST_ROWS';

セッションが変更されました。

SCOTT> l
1 select
2 employee_id,
3 e.department_id,
4 d.department_name
5 from
6 employees e join departments d
7 on e.department_id = d.department_id
8 where
9* employee_id = 203
SCOTT> /

実行計画
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=2011 Card=1481 Bytes=35544)
1 0 NESTED LOOPS (Cost=2011 Card=1481 Bytes=35544)
2 1 TABLE ACCESS (FULL) OF 'EMPLOYEES' (TABLE) (Cost=514 Card=1494 Bytes=11952)
3 1 TABLE ACCESS (BY INDEX ROWID) OF 'DEPARTMENTS' (TABLE) (Cost=1 Card=1 Bytes=16)
4 3 INDEX (UNIQUE SCAN) OF 'PK_DEPARTMENTS' (INDEX (UNIQUE)) (Cost=0 Card=1)


SCOTT>

empoyees表のemployee_id列に索引がないので、FULLスキャンになっているようですね。

ということで、準備完了!

● 早速、仮想索引 (virtual index)を作成してみる! create index文に、 nosegment句を付けるだけ。


  ついでに統計情報も取得する。

SCOTT> create index empid_v_idx on employees(employee_id) nosegment;

索引が作成されました。

SCOTT> exec dbms_stats.gather_index_stats(ownname=>'SCOTT',indname=>'EMPID_V_IDX');

PL/SQLプロシージャが正常に完了しました。

● 実行計画を確認してみる。


おやおや、変化しないですねぇ。って当然です。Virtual Indexは今のところ隠し機能なので、
_use_nosegment_indexes という隠しパラメータを true にセットしないと機能しません。

SCOTT> set autot trace exp
SCOTT> l
1 select
2 employee_id,
3 e.department_id,
4 d.department_name
5 from
6 employees e join departments d
7 on e.department_id = d.department_id
8 where
9* employee_id = 203
SCOTT> /

実行計画
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=2902 Card=2365 Bytes=56760)
1 0 NESTED LOOPS (Cost=2902 Card=2365 Bytes=56760)
2 1 TABLE ACCESS (FULL) OF 'EMPLOYEES' (TABLE) (Cost=513 Card=2384 Bytes=19072)
3 1 TABLE ACCESS (BY INDEX ROWID) OF 'DEPARTMENTS' (TABLE) (Cost=1 Card=1 Bytes=16)
4 3 INDEX (UNIQUE SCAN) OF 'PK_DEPARTMENTS' (INDEX (UNIQUE)) (Cost=0 Card=1)


SCOTT>


● _use_nosegment_indexesパラメータをtrueに設定して再チャレンジ。


おお〜〜〜。employee_id列に非ユニーク索引を作成すれば、このクエリは早くなりそうですね。

SCOTT> alter session set "_use_nosegment_indexes" = true;

セッションが変更されました。

SCOTT> l
1 select
2 employee_id,
3 e.department_id,
4 d.department_name
5 from
6 employees e join departments d
7 on e.department_id = d.department_id
8 where
9* employee_id = 203
SCOTT> /

実行計画
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=273 Card=2365 Bytes=56760)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (TABLE) (Cost=10 Card=88 Bytes=704)
2 1 NESTED LOOPS (Cost=273 Card=2365 Bytes=56760)
3 2 TABLE ACCESS (FULL) OF 'DEPARTMENTS' (TABLE) (Cost=3 Card=27 Bytes=432)
4 2 INDEX (RANGE SCAN) OF 'EMPID_V_IDX' (INDEX) (Cost=1 Card=2384)

SCOTT>


● では、本当の索引を作成したらどうなのでしょうね。


おお〜〜〜。Cost=3042もある〜〜〜。 Virtual Indexの時は、Cost=273なのに〜〜。
だから、隠し機能なんですかね????。 
単純な問合せでは、Virtual Indexとの差はあまりなかったのですが。。。

SCOTT> drop index empid_v_idx;

索引が削除されました。

SCOTT> alter session set "_use_nosegment_indexes" = false;

セッションが変更されました。

SCOTT> create index empid_idx on employees(employee_id);

索引が作成されました。

SCOTT> exec dbms_stats.gather_index_stats(ownname=>'SCOTT',indname=>'EMPID_IDX');

PL/SQLプロシージャが正常に完了しました。

SCOTT> l
1 select
2 employee_id,
3 e.department_id,
4 d.department_name
5 from
6 employees e join departments d
7 on e.department_id = d.department_id
8 where
9* employee_id = 203
SCOTT> /

実行計画
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=3042 Card=1657 Bytes=39768)
1 0 NESTED LOOPS (Cost=3042 Card=1657 Bytes=39768)
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (TABLE) (Cost=1365 Card=1673 Bytes=13384)
3 2 INDEX (RANGE SCAN) OF 'EMPID_IDX' (INDEX) (Cost=4 Card=1673)
4 1 TABLE ACCESS (BY INDEX ROWID) OF 'DEPARTMENTS' (TABLE) (Cost=1 Card=1 Bytes=16)
5 4 INDEX (UNIQUE SCAN) OF 'PK_DEPARTMENTS' (INDEX (UNIQUE)) (Cost=0 Card=1)

SCOTT>


● なぜ? 実行計画に差がでてしまうのだろうか???? (だから隠し機能のままなのか???) 疑問。。。


推測なので、間違っているかもしれないという前提ですが、以下のログを見てもらいたい。
user_objectsには索引として存在しているが、 user_indexesには存在しない。
user_indexesに存在しないということは、dbms_statsパッケージでアナライズしたVirtual Indexの統計情報はどこに格納されるのか???
それがポイントになりそうな気がします。

統計情報の格納場所が無い = 統計情報が無い = 統計情報の欠落 = コストベース・オプティマイザは、統計情報のデフォルト値を利用して実行計画を立てる!
 
ということになるのではないか? と思った次第。従って、実体のある索引とはかけ離れたCostが算出されてしまう。 
これは私個人の推測に過ぎません。なにせ、隠し機能なので、マニュアルにも解説は無いので・・・。

このようなことを踏まえた上で、開発環境で、試してみる程度なら楽しいかもしれませんね。。(趣味の世界の話になっちゃいますけどね。。笑)

今回は、Oracle10g R1を利用した結果なので、Oracle10g R2でも試してみましょうかね。気が向いた時にでも。

SCOTT> l
1 select
2 object_name
3 ,object_type
4 from
5 user_objects
6 where
7* object_type = 'INDEX' and object_name = 'EMPID_V_IDX'
SCOTT> /

OBJECT_NAME OBJECT_TYPE
------------------------------ -------------------
EMPID_V_IDX INDEX

SCOTT> l
1 select
2 index_name
3 ,table_name
4 ,index_type
5 ,uniqueness
6 ,initial_extent
7 ,last_analyzed
8 ,status
9 from
10 user_indexes
11 where
12* index_name = 'EMPID_V_IDX'
SCOTT> /

レコードが選択されませんでした。

SCOTT>

| |

トラックバック


この記事へのトラックバック一覧です: Virtual Index:

コメント

追伸
>おお〜〜〜。Cost=3042もある〜〜〜。 Virtual Indexの時は、Cost=273なのに〜〜。
プラットフォームの異なるOracle10g R2でも全く同じ結果なのでR1特有ということは全くなさそう。

投稿: discus | 2007年7月14日 (土) 21時22分

コメントを書く