goo blog サービス終了のお知らせ 

ORACLE技術研究所~バックナンバー~

メルマガ「ORACLE技術研究所」のバックナンバーページです。

それ以外のバックナンバー

2005-09-16 08:57:24 | お知らせ
素人なりのORACLE検証書籍化に伴い書籍に掲載される内容はH.P.で公開できなくなりました。
対象は以下のようになります。

・INDEXは、どこまで絞れるのか??
・INDEXは行連鎖、行移行するのか?
・ORACLEはBLOCK LEVEL LOCK可能か?
・BITMAP INDEXの謎に迫る!!
・インスタンスが起動しない。。。(書籍版のみ掲載)

これらの内容をお読みになりたい方は11月末下旬発売予定の書籍版を購入してくださるようお願いいたします。

INDEXを使いたい!!

2005-09-16 08:47:40 | バックナンバー
◇INDEXを使いたい!!



 読者の方から質問のメールを受けました。

 普段、このようなことがないので非常に嬉しいです♪



 ↓以下、いただいたメールより抜粋。



-------------------------------------------------------------------------------------------

 SQLのWhere句に「||(パイプ)」を使った連結文字列を使用した場合にインデックスが効きません。

 本などで調べると「||」はインデックスが効かない、と確かに記述してあるのですが、対処法とかありますか?

 FunctionINDEXで「||」で連結させたキーを設定できるのかな?と思って試してみたのですがエラーがでてインデックス作成ができませんでした。ほかにいい方法があれば教えてください。



 tblA

 +---------+---------+---------+---------+

 |A_1(CHAR)|A_2(CHAR)|A_3(CHAR)|A_4(CHAR)|

 +---------+---------+---------+---------+

 | H1601| 01| AAA| 123|

 | H1601| 15| BBB| 123|

 | H1602| 03| CCC| 123|

 | H1603| 18| DDD| 123|

 | H1607| 24| EEE| 123|

 | H1607| 31| FFF| 123|

 +---------+---------+---------+---------+



 上記のようなテーブルがあったとして、以下のようなSQLを実行したいのですが・・・。



 SELECT * FROM tblA WHERE A_1 || A_2 > 'H160301'



 今、インデックスはA_1だけのものとA_1,A_2のものの2パターン作っているのですが、トレースで確認してもFULL検索になっています。

-------------------------------------------------------------------------------------------



 なるほど。

 確かに、このSQLだとINDEXを使えないなぁ。

 では、ちょっと対策を考えてみよう。





◇検証環境構築



 検証環境:

 RDBMS:ORACLE10.1.0

 OS:WindowsXP

 OptimizeMode=ALL_ROWS

 (統計情報未取得)



 --TABLEの作成

 create table tblA (A_1 CHAR(5),A_2 CHAR(2),A_3 CHAR(3),A_4 CHAR(3));



 --RECORDの追加

 insert into tblA values ('H1601','01','AAA','123');

 insert into tblA values ('H1601','15','BBB','123');



 insert into tblA values ('H1602','03','CCC','123');

 insert into tblA values ('H1603','18','DDD','123');

 insert into tblA values ('H1607','24','EEE','123');

 insert into tblA values ('H1607','31','FFF','123');



 commit;



 --INDEXの作成

 create index ind_tblA_1 on tblA(A_1);

 create index ind_tblA_2 on tblA(A_1,A_2);





◇問題



 さて、ここで問題。

 要件を満たしつつINDEXを使用した検索にするには、どうしたら良いか?





◇正解への道のり



 最初、このSQLを見た時、



 SELECT * FROM tblA WHERE A_1 > 'H1603' and A_2 > '01'



 で、いけるんじゃないか??

 なーんだ、簡単簡単

と思った(間違い)

 よーく見るとCOLUMN A_1は年度と月、COLUMN A_2は日付を示していることがわかる。

 つまり要件は

 「指定した日付以降のデータを取得する」

ということになる。



 もし、



 SELECT * FROM tblA WHERE A_1 > 'H1603' and A_2 > '01'



と、やってしまうと本当はH16/03/01以降のデータを抽出したいはずなのにH16/04/01以降のデータしか抽出できない。



 で、どうするか??

 考え方を、ちょっと変えてみる。

 H16/03/01以降というのは

 ・H16/03/02~H16/03/31



 ・H16/04/01~

の2つに分割することができる。



 ということは、つまり

 ・A_1 = 'H1603' and A_2 > '01'

 ・A_1 > 'H1603'

の2つの条件のうち、どちらかを満たすという式にすれば良いワケだ。

 SQLにすると以下のようになる。



 SELECT *

 FROM tblA

 WHERE (A_1 > 'H1603')

 or (A_1 = 'H1603' and A_2 > '01')



 これが正解。

 条件式を2つに分割して要件を満たす。

 これを「困難の分割」と呼ぼう。(探偵学園Q風)





◇実行計画を確認してみよう!



 では、作成したSQLの実行計画を確認してみよう。



 比較の為、まずは変更前のSQLから。



 SQL> SELECT * FROM tblA WHERE A_1 || A_2 > 'H160301';



 Execution Plan

 ----------------------------------------------------------

  0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=2 Card=1 Bytes=51)

  1 0 TABLE ACCESS (FULL) OF 'TBLA' (TABLE) (Cost=2 Card=1 Bytes=51)



 いや、もう気持ち良いくらいTABLE FULL SCANが出てる。



 では、続いて修正したSQLの確認。



 SQL> SELECT *

  2 FROM tblA

  3 WHERE (A_1 > 'H1603')

  4 or (A_1 = 'H1603' and A_2 > '01');



 Execution Plan

 ----------------------------------------------------------

  0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=2 Card=3 Bytes=51)

  1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TBLA' (TABLE) (Cost=2 Card=3 Bytes=51)

  2 1 INDEX (RANGE SCAN) OF 'IND_TBLA_2' (INDEX) (Cost=1 Card=3)



 こちらは、ちゃんとINDEX RANGE SCANになっている。

 ちなみにRULE BASEの場合も確認してみた。



 SQL> SELECT /*+ RULE */ *

  2 FROM tblA

  3 WHERE (A_1 > 'H1603')

  4 or (A_1 = 'H1603' and A_2 > '01');



 Execution Plan

 ----------------------------------------------------------

  0 SELECT STATEMENT Optimizer=HINT: RULE

  1 0 CONCATENATION

  2 1 TABLE ACCESS (BY INDEX ROWID) OF 'TBLA' (TABLE)

  3 2 INDEX (RANGE SCAN) OF 'IND_TBLA_1' (INDEX)

  4 1 TABLE ACCESS (BY INDEX ROWID) OF 'TBLA' (TABLE)

  5 4 INDEX (RANGE SCAN) OF 'IND_TBLA_2' (INDEX)



 RBOの場合、2つのINDEXを使い分けて結果をCONCATENATEしているようだ。





◇他に方法は無いのかな??



 とりあえずは解決したのだが、他に方法は無いのだろうか??

 思いつく方法を試してみる。



 ・||の代わりに+を使ったらどうだろう??



  プログラム言語によっては+を使用することで文字列の結合をしてくれる。

  SQLでは、どうなるのか試してみよう。



  SQL> SELECT * FROM tblA WHERE A_1 + A_2 > 'H160301';

  SELECT * FROM tblA WHERE A_1 + A_2 > 'H160301'

*

  ERROR at line 1:

  ORA-01722: invalid number



  ん?

  エラーが出てしまった。

  エラーの内容を調べてみよう。



  エラー名

   数値が無効です。

  原因

   数値列が無効であるため、文字列から数値への変換は失敗しました。

  処置

   関数または式の中の文字列を確認してください。



  あー、どうやらA_1とA_2を文字列から数値型に変換して計算しようとしているようだ。

  A_1の値は'H1603'なので数値型に変換できない。

  まぁ、仮にA_1に変換できる値が入っていたとしても期待していた結果は得られない。



 ・||の代わりに&を使ったらどうだろう??



  プログラム言語によっては&を使用することで文字列の結合をしてくれる。

  SQLでは、どうなるのか試してみよう。



  SQL> SELECT * FROM tblA WHERE A_1 & A_2 > 'H160301';

  Enter value for a_2:



  おや?

  なんか聞かれてきた。

  どうやら& A_2が変数として認識されてしまったようだ。

  これもダメだな。。。



  誰か良い方法を知ってたら教えてください。

ORACLE10g新機能検証1~FLUSH BUFFER_CACHE~

2005-09-16 08:46:15 | バックナンバー
◇便利な新機能



 ORACLE10gから追加された新機能で非常に便利なコマンドを発見したので紹介したい。

 主にデバッグ用にしか使用できないが・・・。

 コマンドを見るとある程度、どのような内容か推測できるかもしれない。



 ALTER SYSTEM FLUSH BUFFER_CACHE;



 9iまではSHARED_POOLのFLUSHしか実行できなかった。

 しかし、10gからはBUFFER_CACHEのFLUSHも実行できるようになった。

 これで、性能検証の際、バッファに乗ったBLOCKを追い出す為にDBの再起動をする必要がなくなった。

 実はVol.19 ORACLE10g基礎性能検証~追加検証(解答編)~では、このコマンドを多用している。

 このコマンドのおかげで性能試験は本当に楽になったと言える。





◇BUFFER_CACHEって?



 ORACLE Silver Fellow(ORACLE入門)の内容だが簡単に説明を・・・。



 ORACLEのインスタンスはORACLEのバックグラウンドプロセスとSGAから構成されている。

 SGAとはORACLEの使用するメモリ空間のことでSHARED_POOLとBUFFER_CACHEから構成されている。

 BUFFER_CACHEは実データを読み込む為のメモリ空間。

 ORACLEに対する、検索、更新は全てこのBUFFER_CACHEを介して行なう事になる。





◇検証方法



 検証環境

 RDBMS:ORACLE10.1.0

 OS:Windows2000

 Optimizer mode: ALL_ROWS



 今回の検証は「ALTER SYSTEM FLUSH BUFFER_CACHE;」コマンドによりデータがキャッシュから追い出されていることを確認する。

 手順は以下の通り。



 /* sysにてログイン */

 /* DBの再起動 */



 shutdown;



 startup;



 /* 以下、作業用ユーザにてログイン */

 /* SQL Traceの取得 */



 alter session set sql_trace = true ;



 /* SQL1 */



 SELECT SUM(COLUMN3) FROM TEST_2;



 /* SQL2 */



 SELECT SUM(COLUMN3) FROM TEST_2;



 /* sysにて実行 */

 /* FLUSH BUFFER_CACHE */



 ALTER SYSTEM FLUSH BUFFER_CACHE;



 /* 作業用ユーザにて実行 */

 /* SQL3 */



 SELECT SUM(COLUMN3) FROM TEST_2;



 /* SQL Traceの終了 */



 alter session set sql_trace = false ;



 SQL1、SQL2、SQL3は同じSQLに見えて微妙に違う。

 SELECT句とSUM関数の間のスペースの数が、それぞれ

  SQL1 ・・・ 1個

  SQL2 ・・・ 2個

  SQL3 ・・・ 3個

と、なっている。

 これは、3つのSQLを異なるSQLであるとORACLEに認識させる為。



 なぜ異なるSQLだと認識させる必要があるかと言うと、そのほうがTraceFileが見やすくて便利だからである。



 SQL1は再起動直後の為、DiskからBLOCKを読み込む。

 SQL2はSQL1を実行した際にBLOCKがキャッシュに乗っている為、Diskから読み込むBLOCKの数が減っているはず。

 (必要なBLOCKが全てキャッシュに乗っていればDiskは0となる。)

 SQL3はBUFFER_CACHEのFLUSHを行なった後に実行する為、SQL1と同等のDiskを読み込むはず。





◇検証結果



 以下が取得したTraceFileの中身。



 ********************************************************************************



 SELECT SUM(COLUMN3) FROM TEST_2





 call count cpu elapsed disk query current rows

 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 Parse 1 0.01 0.04 0 0 0 0

 Execute 1 0.00 0.00 0 0 0 0

 Fetch 2 0.04 0.14 497 500 0 1

 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 total 4 0.06 0.19 497 500 0 1



 Misses in library cache during parse: 1

 Optimizer mode: ALL_ROWS

 Parsing user id: 64



 Rows Row Source Operation

 ------- ---------------------------------------------------

  1 SORT AGGREGATE (cr=500 pr=497 pw=0 time=145739 us)

  100000 TABLE ACCESS FULL TEST_2 (cr=500 pr=497 pw=0 time=214712 us)



 ********************************************************************************



 SELECT SUM(COLUMN3) FROM TEST_2





 call count cpu elapsed disk query current rows

 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 Parse 1 0.00 0.00 0 0 0 0

 Execute 1 0.00 0.00 0 0 0 0

 Fetch 2 0.03 0.02 0 500 0 1

 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 total 4 0.03 0.02 0 500 0 1



 Misses in library cache during parse: 1

 Optimizer mode: ALL_ROWS

 Parsing user id: 64



 Rows Row Source Operation

 ------- ---------------------------------------------------

  1 SORT AGGREGATE (cr=500 pr=0 pw=0 time=28258 us)

  100000 TABLE ACCESS FULL TEST_2 (cr=500 pr=0 pw=0 time=200057 us)



 ********************************************************************************



 SELECT SUM(COLUMN3) FROM TEST_2





 call count cpu elapsed disk query current rows

 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 Parse 1 0.00 0.00 0 0 0 0

 Execute 1 0.00 0.00 0 0 0 0

 Fetch 2 0.04 0.13 497 500 0 1

 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 total 4 0.04 0.13 497 500 0 1



 Misses in library cache during parse: 1

 Optimizer mode: ALL_ROWS

 Parsing user id: 64



 Rows Row Source Operation

 ------- ---------------------------------------------------

  1 SORT AGGREGATE (cr=500 pr=497 pw=0 time=132281 us)

  100000 TABLE ACCESS FULL TEST_2 (cr=500 pr=497 pw=0 time=308852 us)



 ********************************************************************************



 わかりにくいのでtotalだけ注目して見てみよう。



  call count cpu elapsed disk query current rows

  ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 SQL1 total 4 0.06 0.19 497 500 0 1

 SQL2 total 4 0.03 0.02 0 500 0 1

 SQL3 total 4 0.04 0.13 497 500 0 1



 SQL1ではインスタンス起動後、初めての検索なのでDiskから497Block読み込んでいる。

 SQL2では検索に必要となるDataBlockが全てBUFFER_CACHEに載っているので、Diskから読み込みを行なう必要が無い。

 よってDiskが0になっている。

 SQL3では直前に「ALTER SYSTEM FLUSH BUFFER_CACHE;」コマンドが実行されている為、再度DiksからDataBlockを読み込む必要がある。

 このためSQL1と同じくDiskの値が497となる。

 予想通りの結果。



 上記の結果から「ALTER SYSTEM FLUSH BUFFER_CACHE;」コマンドを実行することによりBUFFER_CACHE上のDataBlockが追い出されている事を確認できた。

実行計画の再利用に関する検証

2005-09-16 08:45:28 | バックナンバー
◇何を検証するのか?



 ORACLEに対する問い合わせを実行すると以下のような処理が内部的に行われる。



 1.SHARED_POOLに実行計画が存在するかのチェック



   ↓1でSHARED_POOLに実行計画が存在した場合



 2.必要なデータがDB_CACHEに存在するかチェック

 3.2で存在しない場合、Diskから読み込み

 4.結果をユーザに返す



   ↓1でSHARED_POOLに実行計画が存在しない場合



 2.SQL構文のチェック(OBJECTの存在チェック)

 3.1で存在しない場合は実行計画の作成

 4.必要なデータがDB_CACHEに存在するかチェック

 5.4で存在しない場合、Diskから読み込み

 6.結果をユーザに返す



 ざっくりとではあるが、このような流れとなる。

 今回、検証するのは1に関わる部分。

 SHARED_POOLに存在するSQLと同一のSQLが発行された場合、実行計画の作成処理をSkipする。

 これはムダな処理を無くし少しでもスループットを速くするため。



 この同一のSQLという定義が、かなり厳密で一言一句、同じでなければならない。

 アルファベットの大文字、小文字の違いすら許してもらえないのだ。



 例えば、

 SELECT SYSDATE FROM DUAL;

と、

 select sysdate from dual;

は、異なるSQLとして認識されてしまう。

 (ORACLEの仕様)



 また、

 SELECT * FROM EMP WHERE EMP_NO = 1

と、

 SELECT * FROM EMP WHERE EMP_NO = 2

も、異なるSQLとして処理される。

 (BIND変数を使用したSQLであれば、同一のSQLとして処理される。)



 今回は、この同一SQL判断がどこまで厳密か検証してみることにした。





◇SHARED_POOLって??



 一応、簡単な説明を・・・

 SHARED_POOLはORACLEのSGAを形成する主要なメモリ領域の1つ。

 SQLに対する実行計画や、実行計画をたてるのに必要となるTABLEやINDEXの情報などがロードされている。





◇検証方法



 検証環境

 RDBMS:ORACLE9.0.1

 OS:Windows2000

 OptimizeMode=Choose

 (AnalyzeしていないためRBOで動作)



 SELECT SYSDATE FROM DUAL;

   

 SELECT SYSDATE

 FROM

 DUAL;



 SELECT SYSDATE FROM DUAL;

   

 上記3種類のSQLを実行しSQL*Traceを取得。

 もし全て同一のSQLとして判断されるのであればParseが1回、Executeが3回となるだろう。

 全て別のSQLとして認識されるのであればParse、Execute共に3回となるはずだ。



 予想としては、

 SELECT SYSDATE FROM DUAL;

と、

 SELECT SYSDATE

 FROM

 DUAL;

くらいは同一のSQLとして処理されるのではないかなぁ??と。





◇検証結果



 取得したTraceFileの中身が以下の内容。



  1 SELECT SYSDATE

  2 FROM

  3 DUAL

4

  5 call count cpu elapsed disk query current rows

  6 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

  7 Parse 1 0.00   0.42 0 0 0 0

  8 Execute 1 0.00 0.47 0 0 0 0

  9 Fetch 2 0.00 0.32 0 1 2 1

 10 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 11 total 4 0.00 1.21 0 1 2 1

12

 13 Misses in library cache during parse: 1

 14 Optimizer goal: CHOOSE

 15 Parsing user id: SYS

16

 17 Rows Row Source Operation

 18 ------- ---------------------------------------------------

 19 1 TABLE ACCESS FULL DUAL

20

 21 ********************************************************************************

22

 23 SELECT SYSDATE)

 24 FROM DUAL

25

 26 call count cpu elapsed disk query current rows

 27 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 28 Parse 1 0.00 0.06 0 0 0 0

 29 Execute 1 0.00 0.02 0 0 0 0

 30 Fetch 2 0.00 0.00 0 1 2 1

 31 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 32 total 4 0.00 0.08 0 1 2 1

33

 34 Misses in library cache during parse: 1

 35 Optimizer goal: CHOOSE

 36 Parsing user id: SYS

37

 38 Rows Row Source Operation

 39 ------- ---------------------------------------------------

 40 1 TABLE ACCESS FULL DUAL

41

 42 ********************************************************************************

43

 44 SELECT SYSDATE

 45 FROM

 46 DUAL

47

 48 call count cpu elapsed disk query current rows

 49 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 50 Parse 1 0.00 0.08 0 0 0 0

 51 Execute 1 0.00 0.00 0 0 0 0

 52 Fetch 2 0.00 0.00 0 1 2 1

 53 ------- ------ -------- ---------- ---------- ---------- ---------- ----------

 54 total 4 0.00 0.08 0 1 2 1

55

 56 Misses in library cache during parse: 1

 57 Optimizer goal: CHOOSE

 58 Parsing user id: SYS

59

 60 Rows Row Source Operation

 61 ------- ---------------------------------------------------

 62 1 TABLE ACCESS FULL DUAL

63

 64 ********************************************************************************



 どうやら全て別のSQLとして認識されたようだ。

 1行目からが改行を挿入したSQL。

 23行目からが普通のSQL。

 44行目からが各単語の間にスペースを3文字入れたSQL。



 改行してるのと普通のは同じSQLとして判断してくれると思ったんだけどなぁ。。。

 おそらくORACLE内部では、

 'SELECT SYSDATE FROM DUAL'

と、

 'SELECT SYSDATE 改行コード FROM 改行コード DUAL'

を比較しているのだろう。



 今回の検証結果としては、

 間にスペースや改行を入れてもORACLEは別SQLとして判断する。

ということになった。