2017年1月 9日 (月)

CDBとPDBの間で迷子になりそう PART3 - SQL*PlusのAutotrace編

今回迷子になったのは、昔からあるSQL*PlusのAutotraceで.

Autotraceの使い方は、$ORACLE_HOME/sqlplus/admin/plustrce.sqlをSYSユーザーで実行し、PLUSTRACEロール作成されたら必要なユーザーへ該当ロールを付与。
あとは、SQL*Plusでautotraceを有効にする。

これだけです。

が、

マルチテナント(シングルテナント含む)化した途端に、迷子になりそうな場面に出会います!
(これにハマったことのあるかたは、#ローカルロール! ってハッシュタグでtw...しなくてもいいですw)

マルチテナントでは、共通ユーザー、共通ロール、ローカルユーザー、ローカルロールという2つのタイプのユーザーとロールが登場しました。
CDB$ROOT/各PDBのディクショナリービュー同様、頭では理解したつもりでも指が勝手タイプして迷子になっちゃいうこともあるw (実はOracleさんも?。。。だったりしてね。

余談はこれぐらいにして、とにかく試して見ましょう。

バージョンは以下のとおり

SYS@orcl12c> select * from v$version;

BANNER CON_ID
-------------------------------------------------------------------------------- ----------
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production 0
PL/SQL Release 12.1.0.2.0 - Production 0
CORE 12.1.0.2.0 Production 0
TNS for Linux: Version 12.1.0.2.0 - Production 0
NLSRTL Version 12.1.0.2.0 - Production 0

マルチテナントにしてあります。(シングルテナントでも同じですが)

SYS@orcl12c> show pdbs

CON_ID CON_NAME OPEN MODE RESTRICTED
---------- ------------------------------ ---------- ----------
2 PDB$SEED READ ONLY NO
3 PDBORCL12C READ WRITE NO
4 PDBORCL12CLONED READ WRITE NO


今日の主役、共通ユーザを作成します。
共通ユーザーとするためには、c##(デフォルト)という接頭子をユーザ名に付加する必要があります。(なれるまで辛いw)

SYS@orcl12c> create user c##hoge identified by hoge
2 default tablespace users
3 temporary tablespace temp
4 quota unlimited on users
5 container=all;

ユーザーが作成されました。


CDB$ROOT含む全PDBにC##HOGEユーザが作成されました。
(実はシステム定義の管理ユーザーは共通ユーザー扱になっていることも忘れちゃいけないんですよね。おそらく同じというか特徴はあるはず)

SYS@orcl12c> select con_id,username,common from containers(dba_users) where username in ('SYS','C##HOGE')

CON_ID USERNAME COM
---------- ------------------------------ ---
1 SYS YES
1 C##HOGE YES
4 SYS YES
4 C##HOGE YES
3 SYS YES
3 C##HOGE YES

※さりげなく、PDB CONTAINER句(赤字部分)を使ってますが、もともとそれで遊びたかっただけなんです。


今後のお遊び向けにいくつかのシステム権限の付与...

SYS@orcl12c> grant create session ,resource to c##hoge container=all;

権限付与が成功しました。

SYS@orcl12c> grant create table, create view to c##hoge container=all;

権限付与が成功しました。

SYS@orcl12c> grant create synonym to c##hoge container=all;

権限付与が成功しました。


共通ユーザーでもSQL*Plusのautotrace使いたいな〜と。
(最近はアダプティブな実行計画の影響で影が薄いAutotraceやExplain planですが、とりあえず見たい時や、実行統計を軽く見たいときには便利なんで)

と、思ったとことから、迷子になりまして、、はいw

CDB$ROOTの共通ユーザー接続して、autotraceを有効にしようとしました。
みなれたエラーがでるわけですよ。ロール作ってないので:)

”SP2-0618: セッション識別子が見つかりません。PLUSTRACEロールが有効かを確認してください。”

SYS@orcl12c> conn c##hoge/hoge
接続されました。
C##HOGE@orcl12c> show con_name

CON_NAME
------------------------------
CDB$ROOT

C##HOGE@orcl12c>
C##HOGE@orcl12c> set autot on
SP2-0618: セッション識別子が見つかりません。PLUSTRACEロールが有効かを確認してください。
SP2-0611: STATISTICSレポートを使用可能にするときにエラーが発生しました。


いつものように、$ORACLE_HOME/sqlplus/admin/plustrce.sqlを実行してPLUSTRACEロールを作成しようとすると。。。。。
なんと、ロールが作成できません!!!

そりゃ、そうですよね。CDB$ROOTには共通なものが載るわけですから、。。。
マルチテナント化してあるので、ロールも共通ロールを作成する必要があります。。。オラクルさん。。。。痛恨のミス?!w

PLUSTRACEというロールをCDB$ROOTに作成しようとするとローカルロールを作成している扱いになるので、共通ロールにしてねというエラーが返されます。

どうすんだよwこれw
と、初っ端から迷子ですw (MOSは未確認。なにか記載されてそうな気はしますがw)

C##HOGE@orcl12c> conn / as sysdba
接続されました。
SYS@orcl12c> @?/sqlplus/admin/plustrce
SYS@orcl12c>

...中略...

1SYS@orcl12c> create role plustrace;
create role plustrace
*
行1でエラーが発生しました。:
ORA-65096: 共通ユーザーまたはロール名が無効です

共通ロールにしろというんだから、素直に共通ロールを作ってみます。
$ORACLE_HOME/sqlplus/admin/plustrce.sqlを元に、ロール名をPLUSTRACEからC##PLUSTRACEに変更したスクリプトを作成しました。

共通ロールについては、以下も参照のこと。
Oracle® Databaseセキュリティ・ガイド 12cリリース1 (12.1) 共通ロールの作成

以下のように、C##(共通ユーザやロールのデフォルト接頭子)を付加し共通ユーザ向けロールを作成すればできるようになりますよね!。使う機会があるかどうかは別ですがw

C##HOGE@orcl12c> !cat $ORACLE_HOME/sqlplus/admin/plustrce_common.sql 

drop role c##plustrace;
create role c##plustrace;

grant select on v_$sesstat to c##plustrace;
grant select on v_$statname to c##plustrace;
grant select on v_$mystat to c##plustrace;
grant c##plustrace to dba with admin option;

C##PLUSTRACEの作成ログ
うまくいきました!!!

SYS@orcl12c> drop role c##plustrace;
drop role c##plustrace
*
行1でエラーが発生しました。:
ORA-01919: ロール'C##PLUSTRACE'は存在しません

SYS@orcl12c> create role c##plustrace;

ロールが作成されました。

SYS@orcl12c> grant select on v_$sesstat to c##plustrace;

権限付与が成功しました。

SYS@orcl12c> grant select on v_$statname to c##plustrace;

権限付与が成功しました。

SYS@orcl12c> grant select on v_$mystat to c##plustrace;

権限付与が成功しました。

SYS@orcl12c> grant c##plustrace to dba with admin option;

権限付与が成功しました。

作成したautotrace用共通ロールをcontainter=allで共通ユーザに付与したのですが。。。。
実は、container=allとしても。PDB側では今まで通り、$ORACLE_HOME/sqlplus/admin/plustrce.sqlを各PDBのSYSユーザー実行し、PLUSTRACEロール(ローカルロール)をPDBに存在する共通ユーザ個別に付与する必要があるようです。(面倒くさい><)

SYS@orcl12c> grant c##plustrace to c##hoge container=all;

C##HOGE@orcl12c> conn / as sysdba
接続されました。
SYS@orcl12c>
SYS@orcl12c> conn c##hoge/hoge
接続されました。
C##HOGE@orcl12c> show con_name

CON_NAME
------------------------------
CDB$ROOT
C##HOGE@orcl12c> set autot trace exp stat
C##HOGE@orcl12c> select * from dual;

実行計画
----------------------------------------------------------
Plan hash value: 272002086

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| DUAL | 1 | 2 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
561 bytes sent via SQL*Net to client
552 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed


PDB側のC##HOGEユーザーには繼承されない。。。。。かるく迷子になってる状況。

C##HOGE@orcl12c> conn c##hoge/hoge@pdborcl12c
接続されました。
C##HOGE@pdborcl12c>
C##HOGE@pdborcl12c> show con_name

CON_NAME
------------------------------
PDBORCL12C
C##HOGE@pdborcl12c> set autot trace exp stat
SP2-0618: セッション識別子が見つかりません。PLUSTRACEロールが有効かを確認してください。
SP2-0611: STATISTICSレポートを使用可能にするときにエラーが発生しました。

各PDBのローカルユーザーは、以前からあるplustrce.sqlを各PDBごとに個別に作成、付与する必要があるみたい...もう完全に迷子w。
(本当はこれをネタしたかったわけじゃなかったんですけどね、トホホ)

C##HOGE@pdborcl12c> conn sys@pdborcl12c as sysdba
パスワードを入力してください:
接続されました。
SYS@pdborcl12c>
SYS@pdborcl12c>
SYS@pdborcl12c> show con_name

CON_NAME
------------------------------
PDBORCL12C
SYS@pdborcl12c>
SYS@pdborcl12c> @?/sqlplus/admin/plustrce

...中略...

SYS@pdborcl12c> conn c##hoge/hoge@pdborcl12c
接続されました。
C##HOGE@pdborcl12c> set autot trace exp stat
C##HOGE@pdborcl12c> select * from dual;


実行計画
----------------------------------------------------------
Plan hash value: 272002086

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| DUAL | 1 | 2 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------

統計
----------------------------------------------------------
1 recursive calls
0 db block gets
3 consistent gets
2 physical reads
0 redo size
561 bytes sent via SQL*Net to client
552 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed


次回、本当に試したかったことへ、つづく....(予定)
久々にその機能本気で使うのかって、ネタになりそうで。。。いいかも(こんな展開は予想してなかっただけに;)


以上、はじめての、 Yahoo! Japan Coworking Space LODGE より


関連エントリー FAQ になりそうな、12c MTA環境での統計履歴管理 ー CDBとPDBの間で迷子になりそう PART2

| | コメント (0) | トラックバック (0)

2016年12月17日 (土)

スタースキーマを扱う実行計画の特徴

JPOUG Advent Calendar 2016の17日目のエントリです。
昨日は、id:kenken08さんのMySQLのsql_modeにあるORACLEとは - kenken0807_DBメモでした。

第三の柴田さんのネタを見て、急遽内容を変更しました。:)
SQLチューニングと対戦格闘ゲームの類似性について語る。- JPOUG Advent Calendar 2016 Day 15 - - ねら~ITエンジニア雑記


DWH系のスタースキーマを扱う実行計画の特徴を簡単にまとめておきたいと思います。(個人的には、in-memory aggregationが今年のハイライトだったのでw)

※サンプルスキーマ:SHスキーマを利用しています。
Installing Sample Schemas

まず、ハッシュ結合とBloom Filterを利用した実行計画です。面倒な準備もなく、癖も少ないので力技でなんとかする系ではよく見かける実行計画です。
Right-Deep Join + Bloom Filter
Right-Deep Joinが可能なのはHash Joinのみです。 意図的に行う場合は、LEADING/USE_HASH/SWAP_JOIN_INPUTSを利用します。
Right-Deep Join Trees and Star Schema Queries
津島博士のパフォーマンス講座 - 第46回 パーティション・プルーニングとハッシュ結合について

スタースキーマでない結合や、NLJではLeft-Deep Joinとなるのが一般的なので見慣れない実行計画だと思う方もいると思いますが、巨大なファクト表よりサイズの小さいディメンジョン表が常にハッシュ結合のビルド表(外部表)になるように結合順序が入れ替えられています。

ハッシュ結合の実行計画としては理にかなっているのですが、超巨大なファクト表との結合がある場合、Exadataをもってしても倒すことができない敵に出会うこともありますw
弱点といえば弱点ですが、方式上難しいところでもあります。
パラレル度を増加させたとしても太刀打ちできないケースもね。。。。とほほ。

Execution Plan
----------------------------------------------------------
Plan hash value: 2503647845

------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1546 | 137K| 3879 (1)| 00:00:01 | | | | | |
| 1 | PX COORDINATOR | | | | | | | | | | |
| 2 | PX SEND QC (ORDER) | :TQ10003 | 1546 | 137K| 3879 (1)| 00:00:01 | | | Q1,03 | P->S | QC (ORDER) |
| 3 | SORT GROUP BY | | 1546 | 137K| 3879 (1)| 00:00:01 | | | Q1,03 | PCWP | |
| 4 | PX RECEIVE | | 1546 | 137K| 3879 (1)| 00:00:01 | | | Q1,03 | PCWP | |
| 5 | PX SEND RANGE | :TQ10002 | 1546 | 137K| 3879 (1)| 00:00:01 | | | Q1,02 | P->P | RANGE |
| 6 | HASH GROUP BY | | 1546 | 137K| 3879 (1)| 00:00:01 | | | Q1,02 | PCWP | |
|* 7 | HASH JOIN | | 580K| 50M| 3875 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 8 | PX RECEIVE | | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
| 9 | PX SEND BROADCAST | :TQ10000 | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | P->P | BROADCAST |
| 10 | PX BLOCK ITERATOR | | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | PCWC | |
| 11 | TABLE ACCESS INMEMORY FULL | COUNTRIES | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | PCWP | |
|* 12 | HASH JOIN | | 580K| 35M| 3872 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 13 | PX RECEIVE | | 55500 | 541K| 8 (13)| 00:00:01 | | | Q1,02 | PCWP | |
| 14 | PX SEND BROADCAST | :TQ10001 | 55500 | 541K| 8 (13)| 00:00:01 | | | Q1,01 | P->P | BROADCAST |
| 15 | PX BLOCK ITERATOR | | 55500 | 541K| 8 (13)| 00:00:01 | | | Q1,01 | PCWC | |
| 16 | TABLE ACCESS INMEMORY FULL| CUSTOMERS | 55500 | 541K| 8 (13)| 00:00:01 | | | Q1,01 | PCWP | |
|* 17 | HASH JOIN | | 580K| 29M| 3864 (1)| 00:00:01 | | | Q1,02 | PCWP | |
|* 18 | TABLE ACCESS INMEMORY FULL | CHANNELS | 2 | 42 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
|* 19 | HASH JOIN | | 1161K| 36M| 3862 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 20 | PART JOIN FILTER CREATE | :BF0000 | 1845 | 22140 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
|* 21 | TABLE ACCESS INMEMORY FULL| TIMES | 1845 | 22140 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
| 22 | PX BLOCK ITERATOR | | 3673K| 73M| 3857 (1)| 00:00:01 |:BF0000|:BF0000| Q1,02 | PCWC | |
| 23 | TABLE ACCESS FULL | SALES | 3673K| 73M| 3857 (1)| 00:00:01 |:BF0000|:BF0000| Q1,02 | PCWP | |
------------------------------------------------------------------------------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 4 because of session
- 1 Sql Plan Directive used for this statement

Star Transformation
巨大なファクト表とディメンジョン表の結合が辛いなら、昔からあるスター変換だ! 
とも思うのですが、スター変換は頑固ものかつ、曲者なのが難点。

頑固さ
ディメンジョン表やファクト表にビットマップ索引や参照整合性制約作成等、利用できるようにするお膳立てができてないと、ピクリとも動きませんw
巨大なファクト表の外部キー列にビットマップ索引を1つ作成するのに、数時間w 複数作成して、さらに、ディメンジョン表との参照整合性制約まで必要なのでまともにやっていると、1日では終わらないことも><
(俺を信じろ、 RELYが利用できるデータの状態であれば楽ではありますが

スター変換の最大の弱点は、ビットマップ索引を利用したROWIDアクセス(以下の実行計画ではId=42の部分)による読み込み件数が多すぎるケースです。性能が伸びなかったり、または、悪化することもあります。
スター変換を利用するかどうかは、ディメンジョン表との結合でファクト表が十分に絞り込めるかにかかっています。

ディメンジョン表との結合キーがビットマップ索引中にあるため、ディメンジョン表とファクト表を結合することなく、ROWIDでファクト表をアクセスして集計することができます。
ファクト表のアクセス量が少ない場合はROWIDアクセスがメリットとなるわけですが、その逆のケースでは、ファクト表はROWIDで1行ごとにアクセスされることになるため、アクセスするファクト表の行数が多くなればなるほど不利になります。

ROWIDによるシングルブロックリードが数十億回繰り返されるとしたら、待機イベントのほとんどが、db file sequential readやcell single block physical read(Exadata)になってしまうことになります。

使いどころを見誤らないようにしたいものです。

Execution Plan
----------------------------------------------------------
Plan hash value: 2513598833

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6490 | 627K| | 5584 (1)| 00:00:01 | | | | | |
| 1 | TEMP TABLE TRANSFORMATION | | | | | | | | | | | |
| 2 | PX COORDINATOR | | | | | | | | | | | |
| 3 | PX SEND QC (RANDOM) | :TQ10000 | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | P->S | QC (RAND) |
| 4 | LOAD AS SELECT (TEMP SEGMENT MERGE) | SYS_TEMP_0FD9D662C_336236 | | | | | | | | Q1,00 | PCWP | |
| 5 | PX BLOCK ITERATOR | | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | PCWC | |
|* 6 | TABLE ACCESS INMEMORY FULL | TIMES | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | PCWP | |
| 7 | PX COORDINATOR | | | | | | | | | | | |
| 8 | PX SEND QC (ORDER) | :TQ20003 | 6490 | 627K| | 5582 (1)| 00:00:01 | | | Q2,03 | P->S | QC (ORDER) |
| 9 | SORT GROUP BY | | 6490 | 627K| 60M| 5582 (1)| 00:00:01 | | | Q2,03 | PCWP | |
| 10 | PX RECEIVE | | 6490 | 627K| | 5582 (1)| 00:00:01 | | | Q2,03 | PCWP | |
| 11 | PX SEND RANGE | :TQ20002 | 6490 | 627K| | 5582 (1)| 00:00:01 | | | Q2,02 | P->P | RANGE |
| 12 | HASH GROUP BY | | 6490 | 627K| 60M| 5582 (1)| 00:00:01 | | | Q2,02 | PCWP | |
|* 13 | HASH JOIN | | 580K| 54M| | 4334 (1)| 00:00:01 | | | Q2,02 | PCWP | |
| 14 | PX RECEIVE | | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q2,02 | PCWP | |
| 15 | PX SEND BROADCAST | :TQ20000 | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q2,00 | P->P | BROADCAST |
| 16 | PX BLOCK ITERATOR | | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q2,00 | PCWC | |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D662C_336236 | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q2,00 | PCWP | |
|* 18 | HASH JOIN | | 580K| 48M| | 4332 (1)| 00:00:01 | | | Q2,02 | PCWP | |
|* 19 | TABLE ACCESS INMEMORY FULL | CHANNELS | 2 | 42 | | 2 (0)| 00:00:01 | | | Q2,02 | PCWP | |
|* 20 | HASH JOIN | | 580K| 36M| | 4329 (1)| 00:00:01 | | | Q2,02 | PCWP | |
| 21 | PX RECEIVE | | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,02 | PCWP | |
| 22 | PX SEND BROADCAST | :TQ20001 | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,01 | P->P | BROADCAST |
|* 23 | HASH JOIN | | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,01 | PCWP | |
| 24 | TABLE ACCESS INMEMORY FULL | COUNTRIES | 23 | 621 | | 2 (0)| 00:00:01 | | | Q2,01 | PCWP | |
| 25 | PX BLOCK ITERATOR | | 55500 | 541K| | 8 (13)| 00:00:01 | | | Q2,01 | PCWC | |
| 26 | TABLE ACCESS INMEMORY FULL | CUSTOMERS | 55500 | 541K| | 8 (13)| 00:00:01 | | | Q2,01 | PCWP | |
| 27 | VIEW | VW_ST_A44449E3 | 580K| 16M| | 4319 (1)| 00:00:01 | | | Q2,02 | PCWP | |
| 28 | NESTED LOOPS | | 580K| 28M| | 4315 (1)| 00:00:01 | | | Q2,02 | PCWP | |
| 29 | PX PARTITION RANGE SUBQUERY | | 580K| 12M| | 15 (14)| 00:00:01 |KEY(SQ)|KEY(SQ)| Q2,02 | PCWC | |
| 30 | BITMAP CONVERSION TO ROWIDS | | 580K| 12M| | 15 (14)| 00:00:01 | | | Q2,02 | PCWP | |
| 31 | BITMAP AND | | | | | | | | | Q2,02 | PCWP | |
| 32 | BITMAP MERGE | | | | | | | | | Q2,02 | PCWP | |
| 33 | BITMAP KEY ITERATION | | | | | | | | | Q2,02 | PCWP | |
| 34 | BUFFER SORT | | | | | | | | | Q2,02 | PCWP | |
|* 35 | TABLE ACCESS INMEMORY FULL| CHANNELS | 2 | 26 | | 2 (0)| 00:00:01 | | | Q2,02 | PCWP | |
|* 36 | BITMAP INDEX RANGE SCAN | SALES_CHANNEL_BIX | | | | | |KEY(SQ)|KEY(SQ)| Q2,02 | PCWP | |
| 37 | BITMAP MERGE | | | | | | | | | Q2,02 | PCWP | |
| 38 | BITMAP KEY ITERATION | | | | | | | | | Q2,02 | PCWP | |
| 39 | BUFFER SORT | | | | | | | | | Q2,02 | PCWP | |
| 40 | TABLE ACCESS FULL | SYS_TEMP_0FD9D662C_336236 | 1845 | 14760 | | 2 (0)| 00:00:01 | | | Q2,02 | PCWP | |
|* 41 | BITMAP INDEX RANGE SCAN | SALES_TIME_BIX | | | | | |KEY(SQ)|KEY(SQ)| Q2,02 | PCWP | |
| 42 | TABLE ACCESS BY USER ROWID | SALES | 1 | 29 | | 4304 (1)| 00:00:01 | ROWID | ROWID | Q2,02 | PCWP | |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 4 because of session
- star transformation used for this statement
- 1 Sql Plan Directive used for this statement

長い前置きでしたが、やっと真打の登場です!
in-memory aggregationとして解説されていることが多いのですが、実行計画を眺めている人間からすると機能名よりvector transformationの方がイメージしやすいので、以下、Vector Tranformation/ベクター変換とします。

Vector Transformation
スター変換同様に、巨大なファクト表とディメンジョン表を直接結合しない点や、パラレル実行時も他の実行計画ではみられない(誤解をおそれずにいうと、全力投球に近いかもw)特徴があります。
索引や参照整合性制約などの作成は不要。
in-memory database関連の機能ではあるのですが、全ての表がinmemory化されていなくても発動させることができます。(inmemory_sizeパラメータの設定は必要となる模様。後述)
最強の力を発揮するのは、全てがinmemoryで動作した場合であることは間違いないわけですが、巨大過ぎるファクト表がinmemory化できるほどメモリが潤沢にあるかというと、そうじゃなかっりしますし。


ベクター変換の動きを簡単に説明すると以下のような感じです。
ディメンジョン表を元に集計結果相当の構造体(in-memory accumulatorと呼ばれる多次元構造体)をメモリ上に構築後、ファクト表を読みながらin-memory accumulator上で集計します!!!(画期的!)
ハッシュ結合が全くなくなるわけではないですが、集計終了後に読み替え目的で少量(この部分が少量じゃないと辛くなるはずなのでよーく確認しておくことをおすすめします)はのハッシュ結合が行われるだけなので、冒頭で紹介した巨大なファクト表とディメンジョン表の結合によるCPUネック部分を華麗に回避していることがわかります。
Right-Deep Join+Bloom Filterで苦しい状況になったら、in-memory aggregationのことを思い出してあげてください。

助けてくれるかもしれません。



Oracle Database In-Memory: In-Memory Aggregation - Oracle White Paper JANUARY 2015


Execution Plan
----------------------------------------------------------
Plan hash value: 3211261687

------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 483 | 60858 | | 3840 (1)| 00:00:01 | | | | | |
| 1 | TEMP TABLE TRANSFORMATION | | | | | | | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D6630_336236 | | | | | | | | | | |
| 3 | PX COORDINATOR | | | | | | | | | | | |
| 4 | PX SEND QC (RANDOM) | :TQ10001 | 21 | 336 | | 3 (34)| 00:00:01 | | | Q1,01 | P->S | QC (RAND) |
| 5 | BUFFER SORT | | 21 | 336 | | 3 (34)| 00:00:01 | | | Q1,01 | PCWP | |
| 6 | VECTOR GROUP BY | | 21 | 336 | | 3 (34)| 00:00:01 | | | Q1,01 | PCWP | |
| 7 | KEY VECTOR CREATE BUFFERED | :KV0000 | 1845 | 29520 | | 3 (34)| 00:00:01 | | | Q1,01 | PCWP | |
| 8 | PX RECEIVE | | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,01 | PCWP | |
| 9 | PX SEND HASH | :TQ10000 | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | P->P | HASH |
| 10 | PX BLOCK ITERATOR | | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | PCWC | |
|* 11 | TABLE ACCESS INMEMORY FULL | TIMES | 1845 | 22140 | | 2 (0)| 00:00:01 | | | Q1,00 | PCWP | |
| 12 | LOAD AS SELECT | SYS_TEMP_0FD9D6631_336236 | | | | | | | | | | |
| 13 | PX COORDINATOR | | | | | | | | | | | |
| 14 | PX SEND QC (RANDOM) | :TQ20001 | 23 | 851 | | 12 (25)| 00:00:01 | | | Q2,01 | P->S | QC (RAND) |
| 15 | HASH GROUP BY | | 23 | 851 | 2624K| 12 (25)| 00:00:01 | | | Q2,01 | PCWP | |
| 16 | KEY VECTOR CREATE BUFFERED | :KV0001 | | | | | | | | Q2,01 | PCWP | |
| 17 | PX RECEIVE | | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,01 | PCWP | |
| 18 | PX SEND HASH | :TQ20000 | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,00 | P->P | HASH |
|* 19 | HASH JOIN | | 55500 | 2005K| | 10 (10)| 00:00:01 | | | Q2,00 | PCWP | |
| 20 | TABLE ACCESS INMEMORY FULL | COUNTRIES | 23 | 621 | | 2 (0)| 00:00:01 | | | Q2,00 | PCWP | |
| 21 | PX BLOCK ITERATOR | | 55500 | 541K| | 8 (13)| 00:00:01 | | | Q2,00 | PCWC | |
| 22 | TABLE ACCESS INMEMORY FULL | CUSTOMERS | 55500 | 541K| | 8 (13)| 00:00:01 | | | Q2,00 | PCWP | |
| 23 | LOAD AS SELECT | SYS_TEMP_0FD9D6632_336236 | | | | | | | | | | |
| 24 | PX COORDINATOR | | | | | | | | | | | |
| 25 | PX SEND QC (RANDOM) | :TQ30001 | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | P->S | QC (RAND) |
| 26 | BUFFER SORT | | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 27 | VECTOR GROUP BY | | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 28 | KEY VECTOR CREATE BUFFERED | :KV0002 | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 29 | PX RECEIVE | | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,01 | PCWP | |
| 30 | PX SEND HASH | :TQ30000 | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | P->P | HASH |
| 31 | PX BLOCK ITERATOR | | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | PCWC | |
|* 32 | TABLE ACCESS INMEMORY FULL | CHANNELS | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | PCWP | |
| 33 | PX COORDINATOR | | | | | | | | | | | |
| 34 | PX SEND QC (ORDER) | :TQ40003 | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | P->S | QC (ORDER) |
| 35 | SORT GROUP BY | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | PCWP | |
| 36 | PX RECEIVE | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | PCWP | |
| 37 | PX SEND RANGE | :TQ40002 | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,02 | P->P | RANGE |
| 38 | HASH GROUP BY | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,02 | PCWP | |
|* 39 | HASH JOIN | | 483 | 60858 | | 3820 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 40 | PX RECEIVE | | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
| 41 | PX SEND BROADCAST | :TQ40000 | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | P->P | BROADCAST |
| 42 | PX BLOCK ITERATOR | | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | PCWC | |
| 43 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6631_336236 | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | PCWP | |
|* 44 | HASH JOIN | | 483 | 42987 | | 3818 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 45 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6630_336236 | 21 | 252 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
|* 46 | HASH JOIN | | 483 | 37191 | | 3816 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 47 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6632_336236 | 2 | 42 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
| 48 | VIEW | VW_VT_AF0F4755 | 483 | 27048 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 49 | HASH GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 50 | PX RECEIVE | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 51 | PX SEND HASH | :TQ40001 | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | P->P | HASH |
| 52 | VECTOR GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | PCWP | |
| 53 | HASH GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | PCWP | |
| 54 | KEY VECTOR USE | :KV0001 | 580K| 18M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 55 | KEY VECTOR USE | :KV0002 | 580K| 16M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 56 | KEY VECTOR USE | :KV0000 | 1161K| 27M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 57 | PX BLOCK ITERATOR | | 3673K| 73M| | 3811 (1)| 00:00:01 |KEY(SQ)|KEY(SQ)| Q4,01 | PCWC | |
|* 58 | TABLE ACCESS FULL| SALES | 3673K| 73M| | 3811 (1)| 00:00:01 |KEY(SQ)|KEY(SQ)| Q4,01 | PCWP | |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 4 because of session
- 2 Sql Plan Directives used for this statement
- vector transformation used for this statement

超絶必殺技にも思えるベクター変換ですが、癖がないわけではありません。(大技につきものの反動というか、なんというかw)

癖 その1)
パラレルクエリー時に割り当てられるサーバープロセスが多く、割り当てられるサーバー数は、KEY VECTORの作成数により大きく変化します。
以下、SQL MONITORのParallel Execution Detailsセクション(抜粋)の比較

並列度は同じでも割り当てるサーバー数はこんなに違う!
VECTOR TRANSFORMATION
KEY VECTORが3つ作成されるベクター変換の場合
(SQL MONITORのParallel Execution Detailsセクションより抜粋)
Parallel Execution Details (DOP=4 , Servers Allocated=32)

KEY VECTORが2つ作成されるベクター変換の場合
Parallel Execution Details (DOP=4 , Servers Allocated=24)

STAR TRANSFORMATION
Parallel Execution Details (DOP=4 , Servers Allocated=12)

Hash Joinのみ
Parallel Execution Details (DOP=4 , Servers Allocated=8)


癖 その2)
スター変換を発動させるための索引作成や、制約作成の煩雑さは無く、全ての表がinmemoryになっていなくても発動させることはできるのですが、発動させるためは、最低限設定しなればならない(ほんと? 不具合?)パラメータが存在します。(

inmemory化する表は無くとも、inmemory_size=100m(設定可能な最小サイズ)に設定しないとVECTOR_TRANSFORMヒントが無視されるという点です。
この制限?を記載しているマニュアルなどは探し出せていないのですが、どこかに記載されているのでしょうか?(いまのところ見つけることができず。。。教えていただけるとうれしいです)


では、最後に、inmemory_size初期化パラメータの設定有無による変化をみてみましょう。

inmemory_size初期化パラメータが設定されている場合にはVECTOR_TRANSFORMヒントでベクター変換を強制できています。

10:36:23 orcl12c@SYSTEM> show parameter inmemory

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
inmemory_clause_default string
inmemory_force string DEFAULT
inmemory_max_populate_servers integer 3
inmemory_query string ENABLE
inmemory_size big integer 512M
inmemory_trickle_repopulate_servers_ integer 1
percent
optimizer_inmemory_aware boolean TRUE


10:37:04 ORCL@SH> set autot trace exp stat
10:56:18 ORCL@SH> @sample3_2

135 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 3211261687

------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 483 | 60858 | | 3967 (1)| 00:00:01 | | | | | |
| 1 | TEMP TABLE TRANSFORMATION | | | | | | | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D6609_33B383 | | | | | | | | | | |
| 3 | PX COORDINATOR | | | | | | | | | | | |
| 4 | PX SEND QC (RANDOM) | :TQ10001 | 21 | 336 | | 20 (5)| 00:00:01 | | | Q1,01 | P->S | QC (RAND) |
| 5 | BUFFER SORT | | 21 | 336 | | 20 (5)| 00:00:01 | | | Q1,01 | PCWP | |
| 6 | VECTOR GROUP BY | | 21 | 336 | | 20 (5)| 00:00:01 | | | Q1,01 | PCWP | |
| 7 | KEY VECTOR CREATE BUFFERED | :KV0000 | 1845 | 29520 | | 20 (5)| 00:00:01 | | | Q1,01 | PCWP | |
| 8 | PX RECEIVE | | 1845 | 22140 | | 19 (0)| 00:00:01 | | | Q1,01 | PCWP | |
| 9 | PX SEND HASH | :TQ10000 | 1845 | 22140 | | 19 (0)| 00:00:01 | | | Q1,00 | P->P | HASH |
| 10 | PX BLOCK ITERATOR | | 1845 | 22140 | | 19 (0)| 00:00:01 | | | Q1,00 | PCWC | |
|* 11 | TABLE ACCESS FULL | TIMES | 1845 | 22140 | | 19 (0)| 00:00:01 | | | Q1,00 | PCWP | |
| 12 | LOAD AS SELECT | SYS_TEMP_0FD9D660A_33B383 | | | | | | | | | | |
| 13 | PX COORDINATOR | | | | | | | | | | | |
| 14 | PX SEND QC (RANDOM) | :TQ20001 | 23 | 851 | | 122 (3)| 00:00:01 | | | Q2,01 | P->S | QC (RAND) |
| 15 | HASH GROUP BY | | 23 | 851 | 2624K| 122 (3)| 00:00:01 | | | Q2,01 | PCWP | |
| 16 | KEY VECTOR CREATE BUFFERED | :KV0001 | | | | | | | | Q2,01 | PCWP | |
| 17 | PX RECEIVE | | 55500 | 2005K| | 119 (0)| 00:00:01 | | | Q2,01 | PCWP | |
| 18 | PX SEND HASH | :TQ20000 | 55500 | 2005K| | 119 (0)| 00:00:01 | | | Q2,00 | P->P | HASH |
|* 19 | HASH JOIN | | 55500 | 2005K| | 119 (0)| 00:00:01 | | | Q2,00 | PCWP | |
| 20 | TABLE ACCESS FULL | COUNTRIES | 23 | 621 | | 2 (0)| 00:00:01 | | | Q2,00 | PCWP | |
| 21 | PX BLOCK ITERATOR | | 55500 | 541K| | 117 (0)| 00:00:01 | | | Q2,00 | PCWC | |
| 22 | TABLE ACCESS FULL | CUSTOMERS | 55500 | 541K| | 117 (0)| 00:00:01 | | | Q2,00 | PCWP | |
| 23 | LOAD AS SELECT | SYS_TEMP_0FD9D660B_33B383 | | | | | | | | | | |
| 24 | PX COORDINATOR | | | | | | | | | | | |
| 25 | PX SEND QC (RANDOM) | :TQ30001 | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | P->S | QC (RAND) |
| 26 | BUFFER SORT | | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 27 | VECTOR GROUP BY | | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 28 | KEY VECTOR CREATE BUFFERED | :KV0002 | 2 | 50 | | 3 (34)| 00:00:01 | | | Q3,01 | PCWP | |
| 29 | PX RECEIVE | | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,01 | PCWP | |
| 30 | PX SEND HASH | :TQ30000 | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | P->P | HASH |
| 31 | PX BLOCK ITERATOR | | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | PCWC | |
|* 32 | TABLE ACCESS FULL | CHANNELS | 2 | 42 | | 2 (0)| 00:00:01 | | | Q3,00 | PCWP | |
| 33 | PX COORDINATOR | | | | | | | | | | | |
| 34 | PX SEND QC (ORDER) | :TQ40003 | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | P->S | QC (ORDER) |
| 35 | SORT GROUP BY | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | PCWP | |
| 36 | PX RECEIVE | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,03 | PCWP | |
| 37 | PX SEND RANGE | :TQ40002 | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,02 | P->P | RANGE |
| 38 | HASH GROUP BY | | 483 | 60858 | | 3821 (1)| 00:00:01 | | | Q4,02 | PCWP | |
|* 39 | HASH JOIN | | 483 | 60858 | | 3820 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 40 | PX RECEIVE | | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
| 41 | PX SEND BROADCAST | :TQ40000 | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | P->P | BROADCAST |
| 42 | PX BLOCK ITERATOR | | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | PCWC | |
| 43 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660A_33B383 | 23 | 851 | | 2 (0)| 00:00:01 | | | Q4,00 | PCWP | |
|* 44 | HASH JOIN | | 483 | 42987 | | 3818 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 45 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6609_33B383 | 21 | 252 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
|* 46 | HASH JOIN | | 483 | 37191 | | 3816 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 47 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660B_33B383 | 2 | 42 | | 2 (0)| 00:00:01 | | | Q4,02 | PCWP | |
| 48 | VIEW | VW_VT_AF0F4755 | 483 | 27048 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 49 | HASH GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 50 | PX RECEIVE | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,02 | PCWP | |
| 51 | PX SEND HASH | :TQ40001 | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | P->P | HASH |
| 52 | VECTOR GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | PCWP | |
| 53 | HASH GROUP BY | | 483 | 15939 | | 3814 (1)| 00:00:01 | | | Q4,01 | PCWP | |
| 54 | KEY VECTOR USE | :KV0001 | 580K| 18M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 55 | KEY VECTOR USE | :KV0002 | 580K| 16M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 56 | KEY VECTOR USE | :KV0000 | 1161K| 27M| | 3811 (1)| 00:00:01 | | | Q4,01 | PCWC | |
| 57 | PX BLOCK ITERATOR | | 3673K| 73M| | 3811 (1)| 00:00:01 |KEY(SQ)|KEY(SQ)| Q4,01 | PCWC | |
|* 58 | TABLE ACCESS FULL| SALES | 3673K| 73M| | 3811 (1)| 00:00:01 |KEY(SQ)|KEY(SQ)| Q4,01 | PCWP | |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

11 - filter("T"."FISCAL_YEAR"=2000 OR "T"."FISCAL_YEAR"=2005 OR "T"."FISCAL_YEAR"=2010 OR "T"."FISCAL_YEAR"=2015 OR "T"."FISCAL_YEAR"=2016)
19 - access("C"."COUNTRY_ID"="R"."COUNTRY_ID")
32 - filter("CH"."CHANNEL_DESC"='Internet' OR "CH"."CHANNEL_DESC"='Partners')
39 - access("ITEM_13"=INTERNAL_FUNCTION("C0") AND "ITEM_14"="C4")
44 - access("ITEM_17"=INTERNAL_FUNCTION("C0") AND "ITEM_18"="C2")
46 - access("ITEM_15"=INTERNAL_FUNCTION("C0") AND "ITEM_16"="C2")
58 - filter(SYS_OP_KEY_VECTOR_FILTER("S"."TIME_ID",:KV0000) AND SYS_OP_KEY_VECTOR_FILTER("S"."CHANNEL_ID",:KV0002))

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 4 because of session
- 2 Sql Plan Directives used for this statement
- vector transformation used for this statement

inmemory_size初期化パラメータを0にし、同一SQL文を実行すると。。。。なんということでしょう。ベクター変換は発動しません!

10:46:22 orcl12c@SYSTEM> show parameter inmemory

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
inmemory_clause_default string
inmemory_force string DEFAULT
inmemory_max_populate_servers integer 0
inmemory_query string ENABLE
inmemory_size big integer 0
inmemory_trickle_repopulate_servers_ integer 1
percent
optimizer_inmemory_aware boolean TRUE


10:47:56 ORCL@SH> @sample3_2

135 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 2503647845

--------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1546 | 137K| 4006 (1)| 00:00:01 | | | | | |
| 1 | PX COORDINATOR | | | | | | | | | | |
| 2 | PX SEND QC (ORDER) | :TQ10003 | 1546 | 137K| 4006 (1)| 00:00:01 | | | Q1,03 | P->S | QC (ORDER) |
| 3 | SORT GROUP BY | | 1546 | 137K| 4006 (1)| 00:00:01 | | | Q1,03 | PCWP | |
| 4 | PX RECEIVE | | 1546 | 137K| 4006 (1)| 00:00:01 | | | Q1,03 | PCWP | |
| 5 | PX SEND RANGE | :TQ10002 | 1546 | 137K| 4006 (1)| 00:00:01 | | | Q1,02 | P->P | RANGE |
| 6 | HASH GROUP BY | | 1546 | 137K| 4006 (1)| 00:00:01 | | | Q1,02 | PCWP | |
|* 7 | HASH JOIN | | 580K| 50M| 4002 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 8 | PX RECEIVE | | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
| 9 | PX SEND BROADCAST | :TQ10000 | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | P->P | BROADCAST |
| 10 | PX BLOCK ITERATOR | | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | PCWC | |
| 11 | TABLE ACCESS FULL | COUNTRIES | 23 | 621 | 2 (0)| 00:00:01 | | | Q1,00 | PCWP | |
|* 12 | HASH JOIN | | 580K| 35M| 3999 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 13 | PX RECEIVE | | 55500 | 541K| 117 (0)| 00:00:01 | | | Q1,02 | PCWP | |
| 14 | PX SEND BROADCAST | :TQ10001 | 55500 | 541K| 117 (0)| 00:00:01 | | | Q1,01 | P->P | BROADCAST |
| 15 | PX BLOCK ITERATOR | | 55500 | 541K| 117 (0)| 00:00:01 | | | Q1,01 | PCWC | |
| 16 | TABLE ACCESS FULL | CUSTOMERS | 55500 | 541K| 117 (0)| 00:00:01 | | | Q1,01 | PCWP | |
|* 17 | HASH JOIN | | 580K| 29M| 3882 (1)| 00:00:01 | | | Q1,02 | PCWP | |
|* 18 | TABLE ACCESS FULL | CHANNELS | 2 | 42 | 2 (0)| 00:00:01 | | | Q1,02 | PCWP | |
|* 19 | HASH JOIN | | 1161K| 36M| 3879 (1)| 00:00:01 | | | Q1,02 | PCWP | |
| 20 | PART JOIN FILTER CREATE| :BF0000 | 1845 | 22140 | 19 (0)| 00:00:01 | | | Q1,02 | PCWP | |
|* 21 | TABLE ACCESS FULL | TIMES | 1845 | 22140 | 19 (0)| 00:00:01 | | | Q1,02 | PCWP | |
| 22 | PX BLOCK ITERATOR | | 3673K| 73M| 3857 (1)| 00:00:01 |:BF0000|:BF0000| Q1,02 | PCWC | |
| 23 | TABLE ACCESS FULL | SALES | 3673K| 73M| 3857 (1)| 00:00:01 |:BF0000|:BF0000| Q1,02 | PCWP | |
--------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

7 - access("C"."COUNTRY_ID"="R"."COUNTRY_ID")
12 - access("S"."CUST_ID"="C"."CUST_ID")
17 - access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
18 - filter("CH"."CHANNEL_DESC"='Internet' OR "CH"."CHANNEL_DESC"='Partners')
19 - access("S"."TIME_ID"="T"."TIME_ID")
21 - filter("T"."FISCAL_YEAR"=2000 OR "T"."FISCAL_YEAR"=2005 OR "T"."FISCAL_YEAR"=2010 OR "T"."FISCAL_YEAR"=2015 OR
"T"."FISCAL_YEAR"=2016)

Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- Degree of Parallelism is 4 because of session
- 1 Sql Plan Directive used for this statement

参考
Database Virtual Box Appliance / Virtual Machine
Installing Sample Schemas
Getting started with Oracle Database In-Memory Part V - Aggregation
津島博士のパフォーマンス講座 - 第54回 Oracle Database In-Memoryについて(2)

利用したSQL
sample1_2.sql
Right-Deep Join (Right-Deep Treeにならない場合には SWAP_JOIN_INPUTSで制御する必要がありますが、SHスキーマでオプティマイザ統計が取得されているのであれば不要.今回の例ではSWAP_JOIN_INPUTSは利用していませんが、オプティマイザは判断を誤るようであれば利用したほうがよいと思います。)

SELECT	
/*+
MONITOR
LEADING(r sum)
USE_HASH(r sum)
*/
sum.fiscal_year
, r.country_name
, sum.channel_class
, sum.sales_amount
FROM
(
SELECT
t.fiscal_year
, c.country_id
, ch.channel_class
, SUM(s.amount_sold) sales_amount
FROM
sales s
, times t
, customers c
, channels ch
WHERE
s.time_id = t.time_id
AND s.cust_id = c.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc in ('Internet','Partners')
AND t.fiscal_year IN (
2000, 2005, 2010, 2015, 2016
)
GROUP BY
ch.channel_class
, c.country_id
, t.fiscal_year
) sum
, countries r
WHERE
sum.country_id = r.country_id
ORDER BY
sum.fiscal_year
, r.country_name
, sum.channel_class
/

sample2_2.sql
スター変換ヒントが必要です。スター変換はデフォルトでOFFに設定されています。
SHスキーマはスタースキーマかつ、スター変換をすぐに試せる環境(ファクト表の外部キーのビットマップ索引やデョメンジョン表への参照整合性制約等)になっています。

SELECT	
/*+
MONITOR
LEADING(r sum)
USE_HASH(r sum)
*/
sum.fiscal_year
, r.country_name
, sum.channel_class
, sum.sales_amount
FROM
(
SELECT
/*+
STAR_TRANSFORMATION
*/
t.fiscal_year
, c.country_id
, ch.channel_class
, SUM(s.amount_sold) sales_amount
FROM
sales s
, times t
, customers c
, channels ch
WHERE
s.time_id = t.time_id
AND s.cust_id = c.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc in ('Internet','Partners')
AND t.fiscal_year IN (
2000, 2005, 2010, 2015, 2016
)
GROUP BY
ch.channel_class
, c.country_id
, t.fiscal_year
) sum
, countries r
WHERE
sum.country_id = r.country_id
ORDER BY
sum.fiscal_year
, r.country_name
, sum.channel_class
/

sample3_2.sql
ベクター変換の例です。ベクター変換のヒントは数種類(ファクト表を記述場合、ディメンジョン表を記述場合)あります。(詳細はv$sql_hintを参照のこと)

SELECT	
/*+
MONITOR
LEADING(r sum)
USE_HASH(r sum)
*/
sum.fiscal_year
, r.country_name
, sum.channel_class
, sum.sales_amount
FROM
(
SELECT
/*+
VECTOR_TRANSFORM
*/
t.fiscal_year
, c.country_id
, ch.channel_class
, SUM(s.amount_sold) sales_amount
FROM
sales s
, times t
, customers c
, channels ch
WHERE
s.time_id = t.time_id
AND s.cust_id = c.cust_id
AND s.channel_id = ch.channel_id
AND ch.channel_desc in ('Internet','Partners')
AND t.fiscal_year IN (
2000, 2005, 2010, 2015, 2016
)
GROUP BY
ch.channel_class
, c.country_id
, t.fiscal_year
) sum
, countries r
WHERE
sum.country_id = r.country_id
ORDER BY
sum.fiscal_year
, r.country_name
, sum.channel_class
/


明日は、@yoshikawさんです! お楽しみに!


俺のターンおわたー!:)

| | コメント (0) | トラックバック (0)

2016年7月19日 (火)

DB Tech Showcase 2016 Tokyo - E35 - SQLチューニング総合診療所的予防医学のセッション資料

DB Tech Showcase 2016 Tokyo - E35 - SQLチューニング総合診療所的予防医学のセッション資料を公開しました。

ぼくとつと、性能試験データの質などのことを言い続ける感じになっていたでしょうか?w

ところで、次回のJPOUG主催のイベントの開催が決まりました。セッション内容は現在準備中ですが、確定次第随時更新します。
お申し込みは以下のイベントベージよりお願いします。

JPOUG in 15 minutes #1

| | コメント (0) | トラックバック (0)

2016年3月27日 (日)

OracleのB*Tree索引にはNULLが含まれる場合があるんです! - その性質を使ってチューニングすることもあるよ:) その3


前回までのMac De Oracle

OracleのB*Tree索引にはNULLが含まれる場合があるんです! - その性質を使ってチューニングすることもあるよ:)
OracleのB*Tree索引にはNULLが含まれる場合があるんです! - その性質を使ってチューニングすることもあるよ:) その2

ということで、続きで〜〜すっ。

こんな表定義で

orcl@SCOTT> desc tab01
Name Null? Type
--------- -------- --------
FOO NUMBER
BAR NUMBER
HOGE NOT NULL CHAR(2)
ID NOT NULL NUMBER

こんな索引があって

****** Index column info : tab01 ******

INDEX_NAME COLUMN_NAME DESC
------------------------------ ------------------------------ ----
IX1_TAB01 FOO ASC

IX2_TAB01 BAR ASC
FOO ASC

IX3_TAB01 ID ASC
FOO ASC

IX4_TAB01 ID ASC
BAR ASC
FOO ASC

PK_TAB01 ID ASC


こんなデータで

orcl@SCOTT> set null [NULL]
orcl@SCOTT> select * from tab01

FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1
1 [NULL] ** 2
[NULL] 1 ** 3
1 1 ** 4


2列の複合索引、どちらの列もNULLだと、やはり、NULLは索引に含まれないので IS NULL検索だと索引は利用されないですよねぇ〜。このような状態ではヒントで索引利用を強制利用させようとしても無理です。

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 index(tab01 ix2_tab01)
5 no_index(tab01 ix4_tab01)
6 no_index(tab01 ix3_tab01)
7 */
8 *
9 from
10 tab01
11 where
12 foo is null
13* and bar is null
     
FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1

・・・略・・・
-------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 8 |
|* 1 | TABLE ACCESS FULL| TAB01 | 1 | 1 | 1 |00:00:00.01 | 8 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(("FOO" IS NULL AND "BAR" IS NULL))

でも、NOT NUL制約の列が1列でも含まれている索引であればNULLは索引に含まれます。(前回までの復習も兼ねた確認)
第1列がWHERE句で記述されていないので索引スキップスキャンになっていますが.....IS NULL検索を索引アクセスだけで行っているのがよく分かる例の一つです:)

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 */
5 *
6 from
7 tab01
8 where
9 foo is null
10* and bar is null

FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1

・・・略・・・
--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 | 1 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB01 | 1 | 1 | 1 |00:00:00.01 | 3 | 1 |
|* 2 | INDEX SKIP SCAN | IX4_TAB01 | 1 | 1 | 1 |00:00:00.01 | 2 | 1 |
--------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("BAR" IS NULL AND "FOO" IS NULL)
filter(("FOO" IS NULL AND "BAR" IS NULL))


最後にもう少しわかりやすい例を。

SQL文を少々書き換えてIndex Only Scanになるようにしました。索引にNULLが含まれていないと索引だけのアクセスで済むわけがないわけで、これ以上わかりやすい例はないと思います:)

まず、索引が利用できない例から。
FOO列とBAR列だけの複合索引をヒントで強制利用させようとしていますが、この索引は2列ともnullableなので2列をIS NULL検索しても索引が利用されません!
全ての列がNULLである場合、キーエントリーは索引に作成されない。単一列でも複合索引でも同じであることが確認できます。

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 index(tab01 ix2_tab01)
5 no_index(tab01 ix4_tab01)
6 no_index(tab01 ix3_tab01)
7 */
8 *
9 from
10 tab01
11 where
12 foo is null
13* and bar is null

FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1

・・・略・・・
-------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 8 |
|* 1 | TABLE ACCESS FULL| TAB01 | 1 | 1 | 1 |00:00:00.01 | 8 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(("FOO" IS NULL AND "BAR" IS NULL))

おっと、
大切なのを忘れてました。

FOO列(nullable(、BAR列(nullable)の複合索引を IS NOT NULL AND IS NULLで検索した場合はどうなるか?
答えは以下の通り。IS NULL と IS NOT NULLの組み合わせでも、索引が利用されます。:)

BAR IS NULLで範囲検索しFOO IS NOT NULLでフィルタリングしています。 NULLが含まれていないと不可能な索引レンジスキャンと索引読み時のフィルタリング!

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 no_index(tab01 ix3_tab01)
5 no_index(tab01 ix4_tab01)
6 */
7 foo
8 , bar
9 from
10 tab01
11 where
12 foo is not null
13* and bar is null

FOO BAR
---------- ----------
1 [NULL]

・・・略・・・
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.02 | 1 | 1 |
|* 1 | INDEX RANGE SCAN| IX2_TAB01 | 1 | 1 | 1 |00:00:00.02 | 1 | 1 |
-------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("BAR" IS NULL)
filter("FOO" IS NOT NULL)

その逆も!

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 no_index(tab01 ix3_tab01)
5 no_index(tab01 ix4_tab01)
6 */
7 foo
8 , bar
9 from
10 tab01
11 where
12 foo is null
13* and bar is not null

FOO BAR
---------- ----------
[NULL] 1

・・・略・・・
----------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 2 |
|* 1 | INDEX SKIP SCAN | IX2_TAB01 | 1 | 1 | 1 |00:00:00.01 | 2 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("FOO" IS NULL)
filter(("FOO" IS NULL AND "BAR" IS NOT NULL))


もういっちょ!
2列とものnullableな索引だとindex fast full scanにはできないけど、index full scanにはできるんですよ〜。

orcl@SCOTT7gt; r
1 select
2 /*+
3 gather_plan_statistics
4 no_index(tab01 ix3_tab01)
5 no_index(tab01 ix4_tab01)
6 */
7 foo
8 , bar
9 from
10 tab01
11 where
12 foo is not null
13* and bar is not null

FOO BAR
---------- ----------
1 1

・・・略・・・
----------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 2 |
|* 1 | INDEX FULL SCAN | IX2_TAB01 | 1 | 1 | 1 |00:00:00.01 | 2 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(("FOO" IS NOT NULL AND "BAR" IS NOT NULL))

Note
-----
- statistics feedback used for this statement

後に、NOT NULL列が含まれる索引なら index fast full scanでもできるはず!
独り言;index only scan+index fast full scanなんてのもセグメントサイズが小さければ物理読み込み量削減には効果があるんですよねぇ〜 ;-)

orcl@SCOTT> r
1 select
2 /*+
3 gather_plan_statistics
4 no_index(tab01 ix3_tab01)
5 index_ffs(tab01 ix4_tab01)
6 no_index(tab01 ix2_tab01)
7 */
8 foo
9 , bar
10 from
11 tab01
12 where
13 foo is not null
14* and bar is not null

FOO BAR
---------- ----------
1 1

・・・略・・・
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.03 | 4 | 1 |
|* 1 | INDEX FAST FULL SCAN| IX4_TAB01 | 1 | 1 | 1 |00:00:00.03 | 4 | 1 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(("FOO" IS NOT NULL AND "BAR" IS NOT NULL))


オラクルの索引にNULLは絶対含まれない、というのは都市伝説! 
IS NOT NULLは索引使えないとかIS NULLは索引使えないというというのも違うんですよね。 

OracleのB*Tree索引では、索引に含まれる全列がNULLの場合以外はNULLが含まれてまっす! というのが正しいですよね!?

この手の問題でピンチになったら、思い出してみてください ;)
USE THE INDEX ONLY SCAN, LUKE! w

| | コメント (0) | トラックバック (0)

2016年3月21日 (月)

OracleのB*Tree索引にはNULLが含まれる場合があるんです! - その性質を使ってチューニングすることもあるよ:) その2

OracleのB*Tree索引にはNULLが含まれる場合があるんです! - その性質を使ってチューニングすることもあるよ:)
の続きです。

念のために、もう一つの主役NULL登場してもらいましょう。

IX1_TAB01索引はNullableなFOO列だけの索引なので、このタイプの索引ではNULLは索引に格納されることはありません。
いくらヒントで索引を指定しても索引にはNULLは格納されていないので全表走査になるはず。

注):索引が多いので意図した索引を利用するようにヒントで固定しています
例2)FOO IS NULL で検索

orcl@SCOTT> r
1 SELECT
2 /*+
3 gather_plan_statistics
4 index(tab01 ix1_tab01)
5 no_index(tab01 ix3_tab01)
6 no_index(tab01 ix4_tab01)
7 */
8 *
9 FROM
10 tab01
11 WHERE
12* foo IS NULL


FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1
[NULL] 1 ** 3


ーーー中略ーーー
Plan hash value: 2044041692

-------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 2 |00:00:00.01 | 8 |
|* 1 | TABLE ACCESS FULL| TAB01 | 1 | 2 | 2 |00:00:00.01 | 8 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("FOO" IS NULL)

想定通り。NULLは格納されていないので索引が利用できず全表走査になっています。
索引からROWIDを取得できないので全表走査してフィルタリングしている箇所がポイントです。

OracleのB*Tree索引にはNULLが入らない都市伝説の始まりはここだったんじゃないか? とい言っている方がいたのですが、ここだけの話が広まってしまい

OracleのB*Tree索引にはNULLは絶対格納されない。

という都市伝説になってしまったのだろうと。。確かに入ってないですからねー。 (^^;;;

では、NULLが索引に含まれる一例を見てみましょう。

例3)ID列(NOT NULL)とFOO列(Nullable)で作成した複合索引を ID=1 AND FOO IS NULLで検索
注):索引が多いので意図した索引を利用するようにヒントで固定しています

Predicate Informationセクションのaccess predicateを見るとわかると思いますが、ID=1 AND FOO IS NULLで索引をアクセスしています。
NULLが索引に含まれていない場合はこのような状況にはなりません。 
複合索引では1列でもNOT NULL制約がり他の列がNullableである場合、NULLは格納されます。

NULLは格納されることもあるんです。マニュアル上の表現は少々わかりにくいとは思いますが、このような状況を指しています。

orcl@SCOTT> r
1 SELECT
2 /*+
3 gather_plan_statistics
4 no_index(tab01 ix1_tab01)
5 index(tab01 ix3_tab01)
6 no_index(tab01 ix4_tab01)
7 */
8 *
9 FROM
10 tab01
11 WHERE
12 id = 1
13* AND foo IS NULL

FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1

ーーー中略ーーー
Plan hash value: 2558346564

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB01 | 1 | 1 | 1 |00:00:00.01 | 3 |
|* 2 | INDEX RANGE SCAN | IX3_TAB01 | 1 | 1 | 1 |00:00:00.01 | 2 |
-----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID"=1 AND "FOO" IS NULL)


少し寄り道。

そういえば、偶になんですが、IS NOT NULLだと索引使うことはないという都市伝説も聞いたことがあります。
それも都市伝説なんですよね。
次の例は、その都市伝説を覆す一例


例4)FOO IS NOT NULL で検索

IX1_TAB01索引は、NullableなFOO列だけの索引です。これまでの検証でこのタイプの索引にはNULLが格納されていないことは確認できると思います。
ようするに、NULLじゃないものは、まるっととズバッと索引に含まれているはずでっす!
なので、FOO IS NOT NULLという検索条件だと索引を使ってくれます。

このような性質を知っているとチューニングに役立つんですよ。
(この例のようなチューニング方法を使ったことは過去数度あるんですよ、苦肉の策でしたけどw NULLの数が非常に多い場合、この手の索引のセグメントサイズは非常に小さくなります(索引に格納されるエントリそのものがNULLを除くと非常に少ない場合)。その性質を利用したチューニング法もあるんです。)

余談NOTE:
INDEX FAST FULL SCANにならない理由:索引にNOT NULL制約の列が少なくとも1列あること、という前提条件を満たせていないからです。チューニングや設計時に必要な知識なので覚えておくと何かの時に役に立つと思います:)

注):索引が多いので意図した索引を利用するようにヒントで固定しています

orcl@SCOTT> r
1 SELECT
2 /*+
3 gather_plan_statistics
4 index(tab01 ix1_tab01)
5 no_index(tab01 ix3_tab01)
6 no_index(tab01 ix4_tab01)
7 */
8 *
9 FROM
10 tab01
11 WHERE
12* foo IS NOT NULL


FOO BAR HO ID
---------- ---------- -- ----------
1 [NULL] ** 2
1 1 ** 4

ーーー中略ーーー
Plan hash value: 2840602802

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 2 |00:00:00.01 | 4 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB01 | 1 | 2 | 2 |00:00:00.01 | 4 |
|* 2 | INDEX FULL SCAN | IX1_TAB01 | 1 | 2 | 2 |00:00:00.01 | 2 |
-----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("FOO" IS NOT NULL)


恐竜博とか**博ってやつは、最後のショップがメインで博のほうはおまけなんじゃないかと最近思ってるw 子供達の目が活き活きしているのはショップに入ってからだ!w

次回へつづく。

| | コメント (0) | トラックバック (0)

2016年3月20日 (日)

OracleのB*Tree索引にはNULLが含まれる場合があるんです! - その性質を使ってチューニングすることもあるよ:)

先日、OracleのB*Tree索引には絶対にNULLが含まれないって思い込んでる人が意外にいるよね〜とか。
OracleのB*Tree索引には絶対NULLが含まれないって都市伝説があるのはなんでだろう。
Oracle® Database概要12cリリース1 (12.1) - 一意索引と非一意索引

みたいなことが話題になって、
ある一人が、「だよね〜、ダンプ見れば含まれてるのわかります。」って言ってて、それ、ふつ〜の人は見ないからw

と思いつつ、私の周りには、やはり、変態が多いことに改めて気づいた次第です。:) はい。


で、
変態じゃない、ごく一般的なエンジニアの方々(ブロックダンプを華麗かつ自然にキメちゃわない方々)向けに、

OracleのB*Tree索引にNULLが含まれているか、NULLが含まれていないかの簡単な確認方法をお伝えしなければ!w

ということで、数回にわけて書いておこうかと思ってます。(予定は未定w)

(実は、このネタとほぼ同じことを1年前ぐらい前に、ローカルかつクローズドな勉強会?、でも使ってました。 最近、やってないみたいですけど)

環境は最近の定番 Oracle Database 12c R1 EE

orcl@SCOTT> select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
PL/SQL Release 12.1.0.2.0 - Production
CORE 12.1.0.2.0 Production
TNS for Linux: Version 12.1.0.2.0 - Production
NLSRTL Version 12.1.0.2.0 - Production


SCOTTスキーマに以下のような表と索引を作成し少ないですが、データを登録
今回の主役は索引とNULLなのでデータ量少なくても必要なパターンが登録できていれば十分です。
(データ量を増やせばチューニングのお題の元ネタにもなると思います。)

orcl@SCOTT> create table tab01 (foo number, bar number, hoge char(2) not null,id number not null);

Table created.

orcl@SCOTT> insert into tab01 values(null,null,'**',1);

1 row created.

orcl@SCOTT> insert into tab01 values(1,null,'**',2);

1 row created.

orcl@SCOTT> insert into tab01 values(null,1,'**',3);

1 row created.

orcl@SCOTT> insert into tab01 values(1,1,'**',4);

1 row created.

orcl@SCOTT> commit;

Commit complete.

orcl@SCOTT> alter table tab01 add constraint pk_tab01 primary key(id) using index;

Table altered.

orcl@SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'TAB01',cascade=>true,no_invalidate=>false);

PL/SQL procedure successfully completed.

orcl@SCOTT> create index ix1_tab01 on tab01 (foo);

Index created.

orcl@SCOTT> create index ix2_tab01 on tab01 (bar,foo);

Index created.

orcl@SCOTT> create index ix3_tab01 on tab01(id,foo);

Index created.

orcl@SCOTT> create index ix4_tab01 on tab01(id,bar,foo);

Index created.


FOOとBAR列はNullableにしています

orcl@SCOTT> desc tab01
Name Null? Type
----------------------------------------- -------- ----------------------------
FOO NUMBER
BAR NUMBER
HOGE NOT NULL CHAR(2)
ID NOT NULL NUMBER

主演の索引たち

NULLが索引でどう扱われるかを確認するため、事前作成の索引をたくさん用意してしました。後から作るの面倒なのでw
これだけ類似索引も含めて多数の索引があると確認時に意図した索引が利用されない可能性も高いため、検証では内容に合わせて利用する索引をヒントで指定することにします。

ちなみに、
普通の環境でこんなに索引があったら、アンチパターン:インデックスショットガンですからね。ご注意ください:) たまにこのような環境に遭遇することはありますが。。。

IX1_TAB01はnullableなFOO列だけの単一列索引
IX2_TAB01はnullableなBAR列とFOO列からなる複合索引
IX3_TAB01とTX4_TAB01は上記の列に加え主キー列のID列を第1キーとする複合索引

としてあります。

orcl@SCOTT> break on table_name on index_name skip 1
orcl@SCOTT> select table_name,index_name,column_name from user_ind_columns where table_name='TAB01' order by table_name,index_name,column_position;

TABLE_NAME INDEX_NAME COLUMN_NAME
------------------------------ ------------------------------ ------------------------------
TAB01 IX1_TAB01 FOO

IX2_TAB01 BAR
FOO

IX3_TAB01 ID
FOO

IX4_TAB01 ID
BAR
FOO

PK_TAB01 ID

登録したデータは以下の通り
NULLは索引とともに今回の主役なので、どこがNULLになっているかメモしておいてくださいませ。

orcl@SCOTT> set null [NULL]
orcl@SCOTT> select * from tab01 order by id;

FOO BAR HO ID
---------- ---------- -- ----------
[NULL] [NULL] ** 1
1 [NULL] ** 2
[NULL] 1 ** 3
1 1 ** 4

さて、索引にNULLが含まれているか、いないか、どうやって確認すると思います? ブロックダンプを華麗にキメきめる以外の方法でw

SQLチューニングをしたことがある方なら一般的に利用してい(と思ってる)機能で簡単に確認できちゃうんですよ。これが。

SQL*Plusのautotraceや、explain plan それに、DBMS_XPLAN.DISPLAY* なファンクションでも確認できます。
SQLclはautotraceにはまだ未対応となっているようですが、それ以外の方法ならはできるようです。
(今回はSQL*Plusを使い、dbms_xplan.display_cursor(format=>'ALLSTATS LAST'))で確認します)


どうやって、どの部分で確認するのか? 
autottraceやDBMS_XPLAN.DISPLAY*などでリストされるpredicate information部分のaccess/filter predicate部分で確認できるんです!


例1)FOO = 1 で検索した例

注):索引が多いので意図した索引を利用するようにヒントで固定しています

おわかりでしょうか?
Predicate Infomation部分から、Id=2のINDEX RANGE SCANで IX1_TAB01索引を FOO=1でアクセスしていることが読み取れますよね?
つまり、1 という値が索引に含まれている(含まれる)からaccess pathとしてIX1_TAB01索引を参照しているわけです。

orcl@SCOTT> r
1 SELECT
2 /*+
3 gather_plan_statistics
4 index(tab01 ix1_tab01)
5 */
6 *
7 FROM
8 tab01
9 WHERE
10* foo = 1

FOO BAR HO ID
---------- ---------- -- ----------
1 [NULL] ** 2
1 1 ** 4

orcl@SCOTT> select * from table(dbms_xplan.display_cursor(format=>'ALLSTATS LAST'));

ーーー中略ーーー

Plan hash value: 3795960549

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 2 |00:00:00.01 | 4 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB01 | 1 | 2 | 2 |00:00:00.01 | 4 |
|* 2 | INDEX RANGE SCAN | IX1_TAB01 | 1 | 2 | 2 |00:00:00.01 | 2 |
-----------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("FOO"=1)


ということで、今日は馬事公苑の散歩は気持ちいいよ〜。

次回へつづく。

| | コメント (0) | トラックバック (0)

2016年3月19日 (土)

Relational Database Index Design and the Optimizers [Kindle版]とUSE THE INDEX, LUKE

時代は繰り返すw のか? という感じで何周目かのIndex only scanとかのネタを元にした資料とか作ることが多くて。。。

索引の設計って、基本的に、機械的だと思っているのですが、皆さんはどう思っているのか気になる今日この頃。
初心者ならともかく、そうでもない感じの方が、腕組みして、眉間にしわ寄せてるのをみるとちょっと心配になっちゃったり。


と昨日のtwで見かけたやつとかも含めて参考になりそうな書籍を載せておきますね。

最初は私のお気に入りの一冊(ハードカバー本でしたが、最近kindle版がでてました。そして私が買った時より高いw
この書籍は2011 db tech showcaseでTom Kyteさんが紹介していたもので、速攻でポチった記憶があります。
そしてその頃は、8,000円台でしたw(円高だったからねぇ)

この書籍ではCovering Index → FAT Index / Semi Covering Index → Semi FAT Indexと記載されています。

Web上でも読めるんですが、書籍化されPDF版もでてます。
SQLパフォーマンス詳解(原文タイトルSQL Performance Explained)


USE THE INDEX, LUKE - 開発者のためのSQLパフォーマンスの全て

| | コメント (0) | トラックバック (0)

2015年12月30日 (水)

Pipelined table function で ascii art

クリスマスも過ぎ、新年へのカウントダウンも始まっていますが、
Pipelined table functionを使って簡単なASCIIアートをJPOUG Advent Calendar 2012描画してましたが中身を公開してなかったな〜
ということで、少々手直しして公開!

公開しておくとPipelined table functionとかtable functionを書かなきゃなった時に思い出しやすいのでw 自分向けなんですけどね。 (^^;;


参考:
Oracle Database Online Documentation 12c Release 1 (12.1)
Unstructured Data and Content Management
13 Using Pipelined and Parallel Table Functions



create or replace type messageTblType as table of varchar2(300)
/


注)
以下関数ではdbms_lock.sleep()を利用しているため、事前にexecute権限を付与しておく必要があります。
また、エラー処理は端折ってます. (^^;;
 

create or replace function tree(lheight pls_integer, linterval_change_color float)
return messageTblType
pipelined
as
height_of_tree pls_integer := lheight;
center pls_integer;
i pls_integer := 1;
siz_of_trunk pls_integer := 3;
pos_of_trunk pls_integer;
interval_change_color float := linterval_change_color;
begin
center := ceil((1 + height_of_tree + ( height_of_tree - 2)) / 2);
pos_of_trunk := case when ceil(height_of_tree / 5) > 10 then 10 else ceil(height_of_tree / 5) end;
for l in 1..75 loop
pipe row(chr(27)||'[2J'||chr(27)||'[1;'||to_char(ceil(dbms_random.value(1,7))+30)||'m');
i := 1;
--draw leaves
while (i < height_of_tree) loop
pipe row(
chr(27)||'['||to_char(i,'FM0999')||';'||to_char(center - i,'FM0999')||'H'||lpad('*', i + (i - 2) + 1, '*')
);
i := i + 1;
end loop;
--draw trunk
for j in -1..pos_of_trunk loop
pipe row(
chr(27)||'['||to_char(j + height_of_tree,'FM0999')||';'||to_char(center - 2,'FM0999')||'H'||lpad('*', siz_of_trunk, '*')
);
end loop;
dbms_lock.sleep(interval_change_color);
end loop;
pipe row(chr(27)||'[0m');
return;
end;
/
show errors


以下、実行コードの例
Treeのサイズを10/20/50、色の変更感覚を0.2秒として実行してます。


set linesize 300
set pagesize 0
set array 1
select * from table(tree(10, 0.2));
select * from table(tree(20, 0.2));
select * from table(tree(50, 0.2));

実行結果はYoutubeで:)


| | コメント (0) | トラックバック (0)

2015年12月26日 (土)

クリスマスのお遊び - SQL de Fractals :)

Twitterを見ていたら以下のtweetが目に入った! お! 面白そう。

元のサイトを覗いてみたら :) w
EXPLAIN EXTENDED -
http://explainextended.com/2013/12/31/happy-new-year-5/

元ネタはPostgreSQLだったので、Oracle Database 12c R1で書き換えて遊んでみた。 :)
遊びながら覚えるSQLってのも面白いよね。久々にLISTAGGなんて使ったよw
20151226_23132


元々あるOracleの方言、階層問合せと、11gR2から実装された階層再帰問合せを組み合わせてます。
PostgreSQLでは、generate_series()関数が利用されていますが、Oracleでは階層問合せかCUBEとrownumを組み合わせて生成するしかないのでちょいとカッコ悪いかもw
ただ、ARRAY_TO_STRING(ARRAY_AGG(s ORDER BY r), '') の箇所は、Oracleなら LISTAGG WITHIN...と1つの関数で代替できたので判りやすいかも。

WITH
q (r, i, rx, ix, g) AS
(
SELECT
CAST(r.r AS DOUBLE PRECISION) * 0.02 AS r
, CAST(i.i AS DOUBLE PRECISION) * 0.02 AS i
, CAST(.0 AS DOUBLE PRECISION) AS rx
, CAST(.0 AS DOUBLE PRECISION) AS ix
, 0 AS g
FROM
(
SELECT
LEVEL - 61 AS r
FROM
DUAL
CONNECT BY
LEVEL <= 80
) r,
(
SELECT
LEVEL - 51 AS i
FROM
DUAL
CONNECT BY
LEVEL <= 100
) i
UNION ALL
SELECT
r
, i
, CASE
WHEN ABS(rx * rx + ix * ix) <= 2
THEN
rx * rx - ix * ix
END + r AS rx
, CASE
WHEN ABS(rx * rx + ix * ix) <= 2
THEN
2 * rx * ix
END + i AS ix
, g + 1 AS g
FROM
q
WHERE
rx IS NOT NULL
AND g < 99
)
SELECT
LISTAGG(s,'') WITHIN GROUP ( ORDER BY r ) AS Mandelbrot
FROM
(
SELECT
i
, r
, SUBSTR(' .:-=+*#%@', MAX(g) / 10 + 1, 1) s
FROM
q
GROUP BY
i
, r
) q
GROUP BY
i
ORDER BY
i
;


実行計画も載せておきますね
こんな実行計画になるのか〜〜〜面白〜い :) :) :)

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 35 | 35 | 0.05 | 8 | 22M |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=1659179395)
============================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
============================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +35 | 1 | 100 | | | |
| 1 | SORT GROUP BY | | 2 | 8 | 1 | +35 | 1 | 100 | 499K | | |
| 2 | VIEW | | 2 | 8 | 1 | +35 | 1 | 8000 | | | |
| 3 | HASH GROUP BY | | 2 | 8 | 34 | +2 | 1 | 8000 | 1M | | |
| 4 | VIEW | | 2 | 8 | 34 | +2 | 1 | 424K | | | |
| 5 | UNION ALL (RECURSIVE WITH) BREADTH FIRST | | | | 35 | +1 | 1 | 424K | 16M | 100.00 | Cpu (35) |
| 6 | MERGE JOIN CARTESIAN | | 1 | 4 | 1 | +2 | 1 | 8000 | | | |
| 7 | VIEW | | 1 | 2 | 1 | +2 | 1 | 80 | | | |
| 8 | CONNECT BY WITHOUT FILTERING | | | | 1 | +2 | 1 | 80 | 2048 | | |
| 9 | FAST DUAL | | 1 | 2 | 1 | +2 | 1 | 1 | | | |
| 10 | BUFFER SORT | | 1 | 4 | 1 | +2 | 80 | 8000 | 4096 | | |
| 11 | VIEW | | 1 | 2 | 1 | +2 | 1 | 100 | | | |
| 12 | CONNECT BY WITHOUT FILTERING | | | | 1 | +2 | 1 | 100 | 2048 | | |
| 13 | FAST DUAL | | 1 | 2 | 1 | +2 | 1 | 1 | | | |
| 14 | RECURSIVE WITH PUMP | | | | 34 | +2 | 100 | 416K | | | |
============================================================================================================================================================


追記!
Sql_de_fractals_frost_pattern


WITH
q (r, i, rx, ix, g) AS
(
SELECT
CAST(r.r AS DOUBLE PRECISION) * 0.0002 AS r
, CAST(i.i AS DOUBLE PRECISION) * 0.0002 AS i
, CAST(r.r AS DOUBLE PRECISION) * 0.0002 AS rx
, CAST(i.i AS DOUBLE PRECISION) * 0.0002 AS ix
, 0 AS g
FROM
(
SELECT
LEVEL - 201 AS r
FROM
DUAL
CONNECT BY
LEVEL <= 120
) r,
(
SELECT
LEVEL - 1 AS i
FROM
DUAL
CONNECT BY
LEVEL <= 100
) i
UNION ALL
SELECT
r
, i
, CASE
WHEN ABS(rx * rx + ix * ix) < 1E+8
THEN
rx * rx - ix * ix
END - 0.70176 AS rx
, CASE
WHEN ABS(rx * rx + ix * ix) < 1E+8
THEN
2 * rx * ix
END + 0.3842 AS ix
, g + 1 AS g
FROM
q
WHERE
rx IS NOT NULL
AND g < 99
)
SELECT
LISTAGG(s,'') WITHIN GROUP ( ORDER BY r ) AS frost_patterns
FROM
(
SELECT
i
, r
, SUBSTR(' .:-=+*#%@', MAX(g) / 10 + 1, 1) s
FROM
q
GROUP BY
i
, r
) q
GROUP BY
i
ORDER BY
i
;


| | コメント (0) | トラックバック (0)

2015年12月 4日 (金)

OTHER_XMLの中身

JPOUG Advent Calendar 2015 - 4日目のエントリーです。

役に立たないから、絶対最後まで読まないでね!!! (^^)

さて、
みなさん、マニュアル読んでますか? 
最近は量が多いので、必要になってから、なりそうだから読むことが多いです。
Oracle® Databaseリファレンス 12cリリース1 (12.1) なんて特にそうです。
ただ、一度読み始めると、いろいろと気づくところもあるわけです...こんな情報が取れるのか! とか。


あ〜、これは、禁断のw マニュアルに記載されてないパラメータを指定すると見れるやつだ!!! とか気づくこともあったり。

例えば、V$SQL_PLANDBA_HIST_SQL_PLANのOTHER_XML列。

この列の説明には実に興味深いことが書かれています。以下の説明、読んでてワクワクしますw 
何が格納されているのでしょう???(もう、みんな知ってるくせに〜)

”アウトライン・データ(同じ計画の再作成に使用できる一連のオプティマイザ・ヒント)”

見たくないですか? 中身。

見たいですよね。私もそうです!


ただ、OTHER_XML列、列名の通り、CLOB型で中身はXMLです!!!
SQL*Plusで普通に表示しようとすると長すぎて読みにくいってのが難点w


ならばXMLにも対応しているSQL文で何とかしてみたいと思います。

ゴニョゴニョ、パタパタ。

できました。

dba_hist_sql_plan向けですが。(v$sql_planビューでも同様のことができます!)
(注:dba_hist_*ビューを参照するにはOracle Diagnostics Packが必要でっす。ですが、v$sql_planを参照するのならオプションはいらないですよね.)

こんな感じです

SYSTEM> !cat report_outline_hints.sql
col outline for a200
set linesize 300
set pagesize 10000
set veri off

SELECT '/*+' AS outline FROM DUAL
UNION ALL
SELECT ' '||'BEGIN_OUTLINE_DATA' FROM DUAL
UNION ALL
SELECT
' '||o.outlinehint
FROM
(
SELECT
EXTRACTVALUE(
VALUE(x)
, '/hint/text()'
) AS outlinehint
FROM
XMLTABLE(
'/*/outline_data/hint'
PASSING(
SELECT
XMLTYPE(other_xml)
FROM
dba_hist_sql_plan
WHERE
other_xml IS NOT NULL
AND sql_id = '&1'
AND plan_hash_value = &2
)
) x
) o
UNION ALL
SELECT ' '||'END_OUTLINE_DATA' FROM DUAL
UNION ALL
SELECT '*/' FROM DUAL
/
set veri on

実行すると以下のような、:) 情報を取り出すことができます!!

SYSTEM> @report_outline_hints gdtyuqcyk8x1c 2236229349

OUTLINE
------------------------------------------------------------------------------------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('12.1.0.2')
DB_VERSION('12.1.0.2')
ALL_ROWS
OUTLINE_LEAF(@"SEL$2")
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "B"@"SEL$1" ("ILM_EXECUTION$"."EXECUTION_ID"))
NO_ACCESS(@"SEL$1" "A"@"SEL$1")
LEADING(@"SEL$1" "B"@"SEL$1" "A"@"SEL$1")
USE_MERGE(@"SEL$1" "A"@"SEL$1")
PQ_FILTER(@"SEL$1" SERIAL)
INDEX(@"SEL$2" "A"@"SEL$2" ("ILM_EXECUTION$"."EXECUTION_ID"))
FULL(@"SEL$2" "B"@"SEL$2")
LEADING(@"SEL$2" "A"@"SEL$2" "B"@"SEL$2")
USE_MERGE(@"SEL$2" "B"@"SEL$2")
USE_HASH_AGGREGATION(@"SEL$2")
END_OUTLINE_DATA
*/

表示された内容に、身に覚え、
いや、見覚えのある方も多いことと思います :) あれでね。そう、あれです。
マニュアルに記載されいないパラメータを利用しなくても取り出せるんです。

ついでなので、マニュアルに記載のないパラメータを使ったDBMS_XPLAN.DISPLAY_AWRで同じ情報をリストしてみました。DBMS_XPLAN.DISPLAY_AWR以外のDISPLAY*関数のformatパラメータでもできます:)
(注: DISPLAY_AWRでAWRを参照するのでOracle Diagnostics Packが必要でっす。 ですが、DISPLAY_CURSOR(),DISPLAY()とDISPLAY_PLAN()ならAWRは参照しないのでオプションはいらないですよね。)

PROMPT
PROMPT ****** display_awr with outline option *********
SELECT
plan_table_output as outline
FROM
TABLE(DBMS_XPLAN.DISPLAY_AWR(sql_id=>'&1',plan_hash_value=>&2,format=>'OUTLINE'))
/


****** DBMS_XPLAN.DISPLAY_AWR with OUTLINE option *******

OUTLINE
------------------------------------------------------------------------------------------
SQL_ID gdtyuqcyk8x1c
--------------------
SELECT B.EXECUTION_ID, NVL(A.N_COUNT,0), A.COMP_TIME FROM ( SELECT
A.EXECUTION_ID, COUNT(*) N_COUNT, NVL(MAX(B.COMPLETION_TIME), SYSDATE)
COMP_TIME FROM SYS.ILM_EXECUTION$ A, SYS.ILM_RESULTS$ B WHERE
EXECUTION_STATE = :B7 AND A.EXECUTION_ID = B.EXECUTION_ID AND
B.JOB_STATUS NOT IN (:B6 , :B5 , :B4 , :B3 , :B2 , :B1 ) GROUP BY
A.EXECUTION_ID )A, ILM_EXECUTION$ B WHERE B.EXECUTION_ID =
A.EXECUTION_ID (+) AND EXECUTION_STATE = :B7 AND (ROWNUM <= :B9 OR :B9
= :B8 )

Plan hash value: 2236229349

-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 4 (100)| |
| 1 | COUNT | | | | | |
| 2 | FILTER | | | | | |
| 3 | MERGE JOIN OUTER | | 1 | 65 | 4 (50)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID | ILM_EXECUTION$ | 1 | 26 | 0 (0)| |
| 5 | INDEX FULL SCAN | PK_TASKID | 1 | | 0 (0)| |
| 6 | SORT JOIN | | 1 | 39 | 4 (50)| 00:00:01 |
| 7 | VIEW | | 1 | 39 | 3 (34)| 00:00:01 |
| 8 | HASH GROUP BY | | 1 | 65 | 3 (34)| 00:00:01 |
| 9 | MERGE JOIN | | 1 | 65 | 3 (34)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID| ILM_EXECUTION$ | 1 | 26 | 0 (0)| |
| 11 | INDEX FULL SCAN | PK_TASKID | 1 | | 0 (0)| |
| 12 | SORT JOIN | | 1 | 39 | 3 (34)| 00:00:01 |
| 13 | TABLE ACCESS FULL | ILM_RESULTS$ | 1 | 39 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

Outline Data
-------------

/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('12.1.0.2')
DB_VERSION('12.1.0.2')
ALL_ROWS
OUTLINE_LEAF(@"SEL$2")
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "B"@"SEL$1" ("ILM_EXECUTION$"."EXECUTION_ID"))
NO_ACCESS(@"SEL$1" "A"@"SEL$1")
LEADING(@"SEL$1" "B"@"SEL$1" "A"@"SEL$1")
USE_MERGE(@"SEL$1" "A"@"SEL$1")
PQ_FILTER(@"SEL$1" SERIAL)
INDEX(@"SEL$2" "A"@"SEL$2" ("ILM_EXECUTION$"."EXECUTION_ID"))
FULL(@"SEL$2" "B"@"SEL$2")
LEADING(@"SEL$2" "A"@"SEL$2" "B"@"SEL$2")
USE_MERGE(@"SEL$2" "B"@"SEL$2")
USE_HASH_AGGREGATION(@"SEL$2")
END_OUTLINE_DATA
*/

ちなみに、11g R2でも動作します!


どうでしたか?役に立たなかったですよね?
以上、OUTLINE HINTSより愛を込めてお送りしました!


参考: (日本語で書かれたエントリーは見当たらない。初か、もしかして!w)

Oracle SQL Plan Stability
http://blog.tanelpoder.com/oracle/performance/sql/oracle-sql-plan-stability/


Plan stability in 10g - using existing cursors to create Stored Outlines and SQL profiles
http://oracle-randolf.blogspot.jp/2009/03/plan-stability-in-10g-using-existing.html


FORCE_MATCH for Stored Outlines and/or SQL Baselines????? – follow up
https://tonyhasler.wordpress.com/2011/12/

How to hint – 1
https://jonathanlewis.wordpress.com/2011/06/08/how-to-hint-1/

dbms_xplan(3)
https://jonathanlewis.wordpress.com/2008/03/06/dbms_xplan3/



追記:

@yoshikawさんに指摘され、EXTRACTVALUE()が11.2で非推奨になっていたとことに気づく orz.

@yoshikawさん、指摘ありがとうございます。 

XMLTABLE().... COLUMNSを使えばEXTRACTVALUE()はいらなかった!!!! ということでEXTRACTVALUEなし版も作りました!!

ただ、CON_IDも見ないとマルチテナントに対応できず、エラーになると気づいたが、
11gにも対応しようとすると、もう一工夫必要なことに気づき、再び、orz.

つづきは、...いずれ...

変更したSQL文は以下のとおり。

SYSTEM> !cat report_outline_hints.sql
col outline for a200
set linesize 300
set pagesize 10000
set veri off

SELECT '/*+' AS outline FROM dual
UNION ALL
SELECT ' '||'BEGIN_OUTLINE_DATA' FROM DUAL
UNION ALL
SELECT
' '||x.outline_hint
FROM
XMLTABLE(
'/*/outline_data/hint/text()'
PASSING(
SELECT
XMLTYPE(other_xml)
FROM
dba_hist_sql_plan
WHERE
other_xml IS NOT NULL
AND sql_id = '&1'
AND plan_hash_value = &2
)
COLUMNS outline_hint PATH '/text()'
) x
UNION ALL
SELECT ' '||'END_OUTLINE_DATA' FROM DUAL
UNION ALL
SELECT '*/' FROM DUAL
/

SYSTEM> @report_outline_hints gdtyuqcyk8x1c 2236229349

OUTLINE
--------------------------------------------------------------------------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('12.1.0.2')
DB_VERSION('12.1.0.2')
ALL_ROWS
OUTLINE_LEAF(@"SEL$2")
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "B"@"SEL$1" ("ILM_EXECUTION$"."EXECUTION_ID"))
NO_ACCESS(@"SEL$1" "A"@"SEL$1")
LEADING(@"SEL$1" "B"@"SEL$1" "A"@"SEL$1")
USE_MERGE(@"SEL$1" "A"@"SEL$1")
PQ_FILTER(@"SEL$1" SERIAL)
INDEX(@"SEL$2" "A"@"SEL$2" ("ILM_EXECUTION$"."EXECUTION_ID"))
FULL(@"SEL$2" "B"@"SEL$2")
LEADING(@"SEL$2" "A"@"SEL$2" "B"@"SEL$2")
USE_MERGE(@"SEL$2" "B"@"SEL$2")
USE_HASH_AGGREGATION(@"SEL$2")
END_OUTLINE_DATA
*/

JPOUG Advent Calendar 2015、12/5は、Yohei Azekatsu さんです。どんな変態ネタが飛び出すのか、乞うご期待!!

| | コメント (0) | トラックバック (0)

2015年11月21日 (土)

WITH clause in-line PL/SQL functionって....

12cの新機能でオイタをしてみたw

with句に関数仕込んで内部でSQL文を実行してオイタしてみました。

実際にこのような使い方するんでしょうか?
これをやるなら素直にスカラー副問合せの方がいいと思うんですね.....
(スカラー副問合せのように使われたら、同じく12cの新機能、Scalar subquery unnestingもできなし。)

なんでこんな機能必要なんだろう?
どのような場面で、有効なんだろう?

今の所、使いどころが思い浮かばないので、誰か教えて〜〜〜〜〜! 
とモヤモヤしてたんですが、今日はお留守番で時間だけはあったので、ごにょごにょとw


SCOTT> set serveroutput on
SCOTT> set feed off
SCOTT> r
1 with
2 function hoge return varchar2
3 deterministic
4 as
5 begin
6 for rec in (select empno,ename from emp order by empno) loop
7 dbms_output.put_line('**** '||rec.empno||':'||rec.ename||' ****');
8 end loop;
9 return null;
10 end;
11 select
12 hoge
13 from
14 dual
15 where
16* hoge is not null

**** 7369:SMITH ****
**** 7499:ALLEN ****
**** 7521:WARD ****
**** 7566:JONES ****
**** 7654:MARTIN ****
**** 7698:BLAKE ****
**** 7782:CLARK ****
**** 7839:KING ****
**** 7844:TURNER ****
**** 7900:JAMES ****
**** 7902:FORD ****
**** 7934:MILLER ****

経過: 00:00:00.01

こんなことをやられると....
実行計画には現れないしw (ちなみにスカラー副問合せは実行計画を見ただけでわかりますです。はい)

ただのチューナーいじめじゃ >< w
SQLトレースなら補足できるけど、なんぎじゃのう。げほげほ

(WITH句のインラインPL/SQLファンクション(名称あってるのかな?)内部でSQL文は実行しない方がいいと思うけど。。。。???)


SCOTT> 
SCOTT> set autot trace exp stat
SCOTT> r
1 with
2 function hoge return varchar2
3 deterministic
4 as
5 begin
6 for rec in (select empno,ename from emp order by empno) loop
7 dbms_output.put_line('**** '||rec.empno||':'||rec.ename||' ****');
8 end loop;
9 return null;
10 end;
11 select
12 hoge
13 from
14 dual
15 where
16* hoge is not null

**** 7369:SMITH ****
**** 7499:ALLEN ****
**** 7521:WARD ****
**** 7566:JONES ****
**** 7654:MARTIN ****
**** 7698:BLAKE ****
**** 7782:CLARK ****
**** 7839:KING ****
**** 7844:TURNER ****
**** 7900:JAMES ****
**** 7902:FORD ****
**** 7934:MILLER ****

経過: 00:00:00.03

実行計画
----------------------------------------------------------
Plan hash value: 4034615273

-----------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 |
|* 1 | FILTER | | | | |
| 2 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("HOGE"() IS NOT NULL)


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
2 consistent gets
0 physical reads
0 redo size
363 bytes sent via SQL*Net to client
541 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed




WITH句のインラインPL/SQLファンクション内でのSQL文の実行はダークサイドな香りしかしないので、使うならデータの加工等、計算中心目的かねぇ〜。とマニュアル読んでたら見つけたw
 
(マニュアルのサンプルも加工のみ! )

SCOTT> r
1 with
2 function hoge(eno number) return varchar2
3 deterministic
4 as
5 begin
6 dbms_output.put_line('Called');
7 return '**** '||eno||' ****';
8 end;
9 select
10 hoge(empno)
11 from
12* emp

HOGE(EMPNO)
---------------------------------------------------------------------
**** 7369 ****
**** 7499 ****
**** 7521 ****
**** 7566 ****
**** 7654 ****
**** 7698 ****
**** 7782 ****
**** 7839 ****
**** 7844 ****
**** 7900 ****
**** 7902 ****
**** 7934 ****
Called
Called
Called
Called
Called
Called
Called
Called
Called
Called
Called
Called
経過: 00:00:00.02

Database SQL Language Reference (12.1)
Database SQL Language Reference (12.1) - SELECT


みなさん、ダークサイドに落ちないように注意しましょうね! :)

| | コメント (0) | トラックバック (0)

2015年6月24日 (水)

理論から学ぶデータベース実践入門 ~リレーショナルモデルによる効率的なSQL

久々に脳にイヤな感じの汗をかいたw

一度読んだだけでは理解できず、何度か繰り返し読み、自分なりに消化する必要のあるタイプの書籍だと思います。
(日々のトレーニング向けに一冊、みたいな)

話は変わりますが、

ワンスアポンナタイム。あるところで。
神チューナーのお一人と取り組んでいたプロジェクトで、イマイチな感じのSQLsを書いてる方々向けに神チューナーが、一言。

ド・モルガンの法則により....」

彼の一言で、場がフリーズしたのを見てニヨニヨしていた私。
(それまで、B*Treeインデックスだの、Index only scanだの、駆動表だの言っていたのにいきなりですからね!w)


そんな一言を投げかけられてもフリーズしない基礎体力をつけるには、この手の書籍はピッタリじゃないかと思うわけです。はい。


| | コメント (0) | トラックバック (0)

2015年5月17日 (日)

Meetup! JPOUG @ Oracle CloudWorld 2015

意外に心?w 時間? に余裕がないのですが、途切れ途切れでも更新していこうかなと50%いや、60%
ぐらい思ってます。

久々の投稿ですが、一ヶ月前のネタです m(_ _)m

Meetup! JPOUG 開催報告

ショートセッション

スペシャル企画 「な〜んでだ?」


ショートセッションのSQLチューニング総合診療Oracle CloudWorld出張所は、db tech showcase 2014 Tokyoの続編のようなw (ような、じゃなくて、続編ですね、キッパリ

いろいろな性能病は無駄な物理I/Oや論理I/Oを削減していくことが、治療の第一歩!。

な〜んでだ? #3は、時間が押してしまい残り5分程度で駆け足で終わらせてしまったので、「な〜んでだ?」は?な雰囲気にできなかったのが心残りでした (笑

AWRを眺めていると、あるような、ないような時間が見えたりしてモヤモヤすることもあると思うんですよね。
そんなときの参考にならないかな? と思ったネタでした。


Oracle Databaseが雲の向こうに行っちゃっても、人工知能型オプティマイザでも登場してこないかぎり、まだ、まだ、人間のサポートが必要だと思うんだよねw いつまで必要なのかは、わからないけど。:)

賢い人工知能型オプティマイザがうまれたとして、各インスタンスごとに性格の違うオプティマイザが成長しちゃったら面白い世界になるかもしれない。
と妄想していると、眠れなくなるので、今日はこのへんで。

| | コメント (0) | トラックバック (0)

2015年1月 2日 (金)

机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!! (実解答編)

JPOUG Advent Calendar 2014 17日目のエントリーで公開したクイズを実解答編です。
実際にOracle Database 12c 12.1.0.2.0を使ってどの表を駆動表(外部表)にするか確認してみます。


予想はあたるか、外れるか...どのような結果が待っているのか..ワクワクします。 :-)
Oracle® Database SQLチューニング・ガイド 12cリリース1(12.1) B71277-02 - 7 結合


前回の予想も参考に....机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!! (予想解答編)

問題1
次に示すSQL文の駆動表(外部表)はどれでしょうか? 

表と索引
create table a1 (
id number primary key
, data varchar2(1000)
) nologging
/
create table a2 (
id number primary key
, data varchar2(1000)
) nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
A1 SYS_C0010377 10000 10000 295
A2 SYS_C0010378 2000 2000 59

SELECT
/* SQL01 */
/*+
MONITOR
USE_NL(a1 a2)
*/
*
FROM
a1
INNER JOIN a2
ON
a1.id = a2.id
WHERE
a1.id BETWEEN 1 AND 100
/


私の答え(予想)
Nested Loop結合かつ、INNER JOINですから駆動表(外部表)はデータセットの小さい方。
(Nested Loop結合でOUTER JOINだと結合順は固定されるので駆動表は見つけやすいですが...INNER JOINの場合はそうはいかないですよね。ちなみに、最近のOracleだと、OUTER JOINでHash結合だと外部表を入れれかえて最適化するケースもあります。いずれどこかでそのネタをやると思います。)

ただ、結合条件キーはどちらも主キーですし、WHERE a1.id BETWEEN 1 AND 100は、a2表にも適用されることになるでしょうから、a1、a2どちらも最大で100行ヒットする可能性はあります。
どちらでも正解になる可能性はありますが...w (最初から引っ掛けかよw

細かい条件は提示していないので、提示した情報だけで机上で駆動表(外部表)を決めるとすれば、全体のサイズが小さい、a2でいいんじゃないでしょうか?(私ならそうします。机上ですから)
(当初、もう少し詳細な情報を提示しようとしていたのですが忘れてました...なのでa1だと思った方も可能性は五分五分ですね。前提条件不足でした。...ごめんなさい。ごめんなさい。)

Oracle Database 12c 12.1.0.2.0の動き
以下、SQLモニタリングレポートをみると、駆動表は、 a2表でした。 :)
ちなみに、a1を駆動表にした場合は、若干Buffer Getsが増加していました。誤差の範囲ですが...

SQL Monitoring Report

Global Stats
=======================================
| Elapsed | Other | Fetch | Buffer |
| Time(s) | Waits(s) | Calls | Gets |
=======================================
| 0.00 | 0.00 | 3 | 37 |
=======================================

SQL Plan Monitoring Details (Plan Hash Value=3695212685)
=====================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (%) | (# samples) |
=====================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 20 | | |
| 1 | NESTED LOOPS | | 20 | 23 | 1 | +0 | 1 | 20 | | |
| 2 | NESTED LOOPS | | 20 | 23 | 1 | +0 | 1 | 20 | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED | A2 | 20 | 3 | 1 | +0 | 1 | 20 | | |
| 4 | INDEX RANGE SCAN | SYS_C0010378 | 20 | 2 | 1 | +0 | 1 | 20 | | |
| 5 | INDEX UNIQUE SCAN | SYS_C0010377 | 1 | | 1 | +0 | 20 | 20 | | |
| 6 | TABLE ACCESS BY INDEX ROWID | A1 | 1 | 1 | 1 | +0 | 20 | 20 | | |
=====================================================================================================================================================

問題2
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題1と同じです。)

SELECT
/* SQL02 */
/*+
MONITOR
USE_HASH(a1 a2)
*/
*
FROM
a1
INNER JOIN a2
ON
a1.id = a2.id
/

私の答え(予想)
Hash結合でINNER JOINかつ、WHERE句はないのでこの問題は簡単ですよね! (これは迷わないはず!!!)

Hash結合も外部表はデータセットの小さい方ですから...この場合だと、a2が外部表になるはず。

Oracle Database 12c 12.1.0.2.0の動き
以下、SQLモニタリングレポートから....外部表は、予想通りのa2

SQL Monitoring Report

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.01 | 0.01 | 0.00 | 135 | 497 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=1713954154)
==================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
==================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 2000 | | | |
| 1 | HASH JOIN | | 2000 | 121 | 1 | +0 | 1 | 2000 | 1M | | |
| 2 | TABLE ACCESS FULL | A2 | 2000 | 19 | 1 | +0 | 1 | 2000 | | | |
| 3 | TABLE ACCESS FULL | A1 | 10000 | 102 | 1 | +0 | 1 | 10000 | | | |
==================================================================================================================================


問題3
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
なお、D1とD2の多重度は、D1:D2 = 1:100
(個人的にUMLの多重度表記のほうが好きなので、UML表記の多重度で記述します。

表と索引
create table d1 (
id number
, data varchar2(1000)
) nologging
/
alter table d1 add constraint pk_d1 primary key (id) using index nologging
/
create table d2 (
id number not null
, seq# number not null
, data varchar2(1000)
) nologging
/
alter table d2 add constraint pk_d2 primary key (id, seq#) using index nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
D1 PK_D1 200 200 9
D2 PK_D2 20000 20000 870

SELECT
/* SQL03 */
/*+
MONITOR
*/
*
FROM
d2
WHERE
EXISTS (
SELECT
1
FROM
d1
WHERE
d1.id = d2.id
AND d1.id IN (1,5)
)
/


私の答え(予想)
これはちょっと意地悪な問題ですが、わかる人ならわかるはず。だと思って(信じて)作った問題です。 :)

最近のオプティマイザは、相関副問合せを可能であれば結合に書き換える(unnest)傾向が強いのをご存知でしょうか? 
となれば、方向はだいだい見えてきます。

d1.id IN (1,5)から最大で2件ヒットすると予想できますよね。
さらに、Nested Loop結合に書き換え、d2を内部表として結合できれば無駄がない。
つまり、駆動表は d1が理想的なはず。

d2を外部表にしてしまうと、WHERE条件がないので結合に置き換えてしまうとHash結合または、d2を全表走査して相関副問合せを都度実行のいづれかになってしまうでしょうね。(unnestの傾向が強いのでHash結合になる可能性が高そう)
ヒットするデータ件数から考えると、どちらも無駄なデータアクセスが多くなる可能性は高いのではないでしょうか。


Oracle Database 12c 12.1.0.2.0の動き
おおお〜。予想通り、相関副問合せは、unnestされて結合に書き換えられています。 さらに 12cの新機能であるAdaptive Joinになっています。興味深い。

で本題である、外部表はどうなっているかというと....pk_d1をindex only scanしてNested Loop結合になっています。つまり、駆動表は、予想通り、d1になっています。Yahoo!
id=1でHash結合がありますが、試行はしたようですが、実際にはNested Loop結合だったようです。

SQL Monitoring Report

Global Stats
=======================================
| Elapsed | Other | Fetch | Buffer |
| Time(s) | Waits(s) | Calls | Gets |
=======================================
| 0.01 | 0.01 | 15 | 44 |
=======================================

SQL Plan Monitoring Details (Plan Hash Value=804853015)
==============================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (%) | (# samples) |
==============================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 0 | | |
| 1 | HASH JOIN | | 200 | 5 | 1 | +0 | 1 | 0 | | |
| 2 | NESTED LOOPS | | 200 | 5 | 1 | +0 | 1 | 200 | | |
| 3 | NESTED LOOPS | | 200 | 5 | 1 | +0 | 1 | 200 | | |
| 4 | STATISTICS COLLECTOR | | | | 1 | +0 | 1 | 1 | | |
| 5 | INLIST ITERATOR | | | | 1 | +0 | 1 | 2 | | |
| 6 | INDEX UNIQUE SCAN | PK_D1 | 2 | 1 | 1 | +0 | 2 | 2 | | |
| 7 | INDEX RANGE SCAN | PK_D2 | 1 | 1 | 1 | +0 | 2 | 200 | | |
| 8 | TABLE ACCESS BY INDEX ROWID | D2 | 100 | 2 | 1 | +0 | 200 | 200 | | |
| 9 | INLIST ITERATOR | | | | | | | | | |
| 10 | TABLE ACCESS BY INDEX ROWID BATCHED | D2 | 100 | 2 | | | | | | |
| 11 | INDEX RANGE SCAN | PK_D2 | 1 | 1 | | | | | | |
==============================================================================================================================================


問題4
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題3と同じです。)

SELECT
/* SQL04 */
/*+
MONITOR
*/
*
FROM
d1
INNER JOIN d2
ON
d1.id > d2.id
AND d2.id BETWEEN 1 AND 5
AND d2.seq# BETWEEN 2 AND 4
/

私の答え(予想)
結合条件が ">" であることに気付きましたか? ;)


これは、Nested Loop結合にも、Hash結合にもなりません。 Merge(sort/merge)結合が選択されます。

内部表は索引が利用できないのでソートが発生します。(ソートは避けられない)
外部表では索引を利用してソート処理が回避可能であれば、回避する。

つまりソート処理の影響を最小にしようとするはず、なので...

d1:d2=1:100という比率からd2のソート処理を重いと判断し回避するために外部表にするだろうなぁ〜〜。
私は、d2が外部表になるだろうと予想しています。
(直積じゃないMerge結合なんて最近お目にかかったことないのですが...)

余談)
Hash結合のない時代のOracleで、Merge結合の実行計画を見せられて、なんでソートしてるんですかね〜と、某ベンダーの方に質問されて一瞬固まったことを思い出したw


Oracle Database 12c 12.1.0.2.0の動き
良かったw。 予想どおりソートを回避したようで、 外部表は、 d2ですね。

SQL Monitoring Report

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.02 | 0.01 | 0.01 | 198 | 48 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=705618498)
=============================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
=============================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 2955 | | | |
| 1 | MERGE JOIN | | 3966 | 10 | 1 | +0 | 1 | 2955 | | | |
| 2 | TABLE ACCESS BY INDEX ROWID | D2 | 20 | 4 | 1 | +0 | 1 | 15 | | | |
| 3 | INDEX RANGE SCAN | PK_D2 | 20 | 3 | 1 | +0 | 1 | 15 | | | |
| 4 | SORT JOIN | | 199 | 6 | 1 | +0 | 15 | 2955 | 145K | | |
| 5 | TABLE ACCESS FULL | D1 | 199 | 5 | 1 | +0 | 1 | 199 | | | |
=============================================================================================================================================


問題5
次に示すSQL文の駆動表(外部表)はどれでしょうか? 

表と索引定義
create table b1 (
id number
, data varchar2(1000)
) nologging
/
alter table b1 add constraint pk_b1 primary key(id) using index nologging
/
create table b3 (
id number
, seq# number
, data varchar2(1000)
) nologging
/
alter table b3 add constraint pk_b3 primary key (id, seq#) using index nologging
/
create table b2 (
id number
, seq# number
, subseq# number
, data varchar2(1000)
) nologging
/
alter table b2 add constraint pk_b2 primary key (id ,seq#, subseq#) using index nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
B1 PK_B1 20000 20000 870
B2 PK_B2 5000 5000 228
B3 PK_B3 500 500 23

ERDと多重度
b1 : b3 = 1 : 0..2
b3 : b2 = 1 : 0..10
(UML表記の多重度で記述しています。
20141221_120733

SELECT
/* SQL05 */
/*+
MONITOR
USE_HASH(b1 b3 b2)
*/
*
FROM
b1
INNER JOIN b3
ON
b1.id = b3.id
INNER JOIN b2
ON
b3.id = b2.id
AND b3.seq# = b2.seq#
/

私の答え(予想)
INNER JOINでHash結合かつ、WHERE句もないので簡単な問題ですよね。

外部表は、b3

ついでに、続けると、 b3とb2を結合すると最大で5000件、b3とb1を結合した場合、最大で500件なので、 結合順として理想的なのは、 b3, b1, b2 ですよね。


Oracle Database 12c 12.1.0.2.0の動き
結果は、予想通りでした〜。これは簡単だから!。

SQL Monitoring Report

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.06 | 0.04 | 0.02 | 335 | 1476 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=3711653655)
===================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
===================================================================================================================================
| 0 | SELECT STATEMENT | | | | 1 | +0 | 1 | 5000 | | | |
| 1 | HASH JOIN | | 5000 | 315 | 1 | +0 | 1 | 5000 | 1M | | |
| 2 | HASH JOIN | | 500 | 247 | 1 | +0 | 1 | 500 | 1M | | |
| 3 | TABLE ACCESS FULL | B3 | 500 | 9 | 1 | +0 | 1 | 500 | | | |
| 4 | TABLE ACCESS FULL | B1 | 20000 | 238 | 1 | +0 | 1 | 20000 | | | |
| 5 | TABLE ACCESS FULL | B2 | 5000 | 68 | 1 | +0 | 1 | 5000 | | | |
===================================================================================================================================


問題6
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題5と同じです。)

SELECT
/* SQL06 */
/*+
MONITOR
USE_HASH(b1 b3 b2)
*/
*
FROM
b1
LEFT OUTER JOIN b3
ON
b1.id = b3.id
LEFT OUTER JOIN b2
ON
b3.id = b2.id
AND b3.seq# = b2.seq#
/

私の答え(予想)
OUTER JOINなのですが、Hash結合なので、多分、予想通りにはならないだろうな〜〜と。

最近のオプティマイザは外部結合かつHash結合の場合、外部表と内部表をデータセットサイズに応じて入れ替える傾向が強いんですよね。PGAの使用サイズが小さくなるように....

とは言っても、机上の話なので、入れ替えないとしたらどうなるだろう..と、外部結合なので、内部表は、そのまま内部表として結合したほうがわかりやすいといえばわかりやすい。
(統計情報に乖離がある場合には入れ替えないほうが良い結果だったりすることもあります。)

注)()内を先に結合するものとします。
入れ替えなかった場合は、記述したままなので、(外部表、内部表)=外部表、内部表ということで、 (b1, b3), b2
データセットの小さい順に従えば、外部表と、内部表をいれかえて、b3, b1この結果セットの最大件数は元の外部表がb1ですから20,250件、b2が5,000件なので、また外部表と内部表を入れ替えて、結果として、b2, (b3, b1)ってことに...
(内部的に結合順が入れ替えられ、Right Joinに置き換えられることが多いんですよね〜いつ頃からだろう...10gあたりか。)

で、机上ならどちらを取るかということですが、机上では、ひとまず、SQL文の通りとしておくケースが多いです。あくまで私の場合ですよ。(難しいんですよ、これ。外れる可能性大w)
オプティマイザが内部的に外部表と内部表を入れ替えて問題がなければよし、問題があれば、内部的な入れ替えをヒントで止めるというのが私の基本方針なので、この辺りは、チューナーさんのさじ加減次第じゃないかなぁ、と思っています。

あはは、むずかし過ぎたか... 机上だと orz.

Oracle Database 12c 12.1.0.2.0の動き
結果を見てみると〜〜〜〜〜っ。恐れていたw 外部表と内部表の置き換え操作(LEFT OUTERがRIGHT OUTER)になってますね〜っ。あはは、見事に、外れました....orz.

SQL Monitoring Report

Global Stats
=================================================
| Elapsed | Cpu | Other | Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
=================================================
| 0.34 | 0.21 | 0.13 | 1651 | 2648 |
=================================================

SQL Plan Monitoring Details (Plan Hash Value=1453650298)
======================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Mem | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (Max) | (%) | (# samples) |
======================================================================================================================================
| 0 | SELECT STATEMENT | | | | 4 | +0 | 1 | 24750 | | | |
| 1 | HASH JOIN RIGHT OUTER | | 203K | 316 | 4 | +0 | 1 | 24750 | 3M | | |
| 2 | TABLE ACCESS FULL | B2 | 5000 | 68 | 1 | +0 | 1 | 5000 | | | |
| 3 | HASH JOIN RIGHT OUTER | | 20250 | 247 | 4 | +0 | 1 | 20250 | 1M | | |
| 4 | TABLE ACCESS FULL | B3 | 500 | 9 | 1 | +0 | 1 | 500 | | | |
| 5 | TABLE ACCESS FULL | B1 | 20000 | 238 | 4 | +0 | 1 | 20000 | | | |
======================================================================================================================================

問題ごとの解説をおまけで何回かに分けて、書いた方が良さそうだなw

ということで、おまけエントリーを書くかも...し、れ、な、い。


机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!!
机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!! (予想解答編)

| | コメント (1) | トラックバック (0)

2015年1月 1日 (木)

机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!! (予想解答編)

JPOUG Advent Calendar 2014 17日目のエントリーで公開したクイズの予想解答編です。
机上ですし、限られた情報しか提示していないので、実際にやってみたら違う...ということも十分ありえます。 (^^;;;;


外部表はどれがいいか予想できると、いろいろな制限のあるチューニング現場では役立つこともあるんです。LEADINGヒント等で結合順を考える場合にも役立ちますyo!
机上の予想なので間違うこともあります。オプティマイザも統計情報から予想しているわけで(Adaptiveな機能を除く)ハズすことも多いですから... (^^

Oracle® Database SQLチューニング・ガイド 12cリリース1(12.1) B71277-02 - 7 結合


問題1
次に示すSQL文の駆動表(外部表)はどれでしょうか? 

表と索引
create table a1 (
id number primary key
, data varchar2(1000)
) nologging
/
create table a2 (
id number primary key
, data varchar2(1000)
) nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
A1 SYS_C0010377 10000 10000 295
A2 SYS_C0010378 2000 2000 59

SELECT
/* SQL01 */
/*+
MONITOR
USE_NL(a1 a2)
*/
*
FROM
a1
INNER JOIN a2
ON
a1.id = a2.id
WHERE
a1.id BETWEEN 1 AND 100
/

私の答え(予想)
Nested Loop結合かつ、INNER JOINですから駆動表(外部表)はデータセットの小さい方。
(Nested Loop結合でOUTER JOINだと結合順は固定されるので駆動表は見つけやすいですが...INNER JOINの場合はそうはいかないですよね)

ただ、結合条件キーはどちらも主キーですし、WHERE a1.id BETWEEN 1 AND 100は、a2表にも適用されることになるでしょうから、a1、a2どちらも最大で100行ヒットする可能性はあります。
どちらでも正解になる可能性はありますが...w (最初から引っ掛けかよw

細かい条件は提示していないので、提示した情報だけで机上で駆動表(外部表)を決めるとすれば、全体のサイズが小さい、a2でいいんじゃないでしょうか?(私ならそうします。机上ですから)
(当初、もう少し詳細な情報を提示しようとしていたのですが忘れてました...なのでa1だと思った方も可能性は五分五分ですね。前提条件不足でした。...ごめんなさい。ごめんなさい。)


問題2
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題1と同じです。)

SELECT
/* SQL02 */
/*+
MONITOR
USE_HASH(a1 a2)
*/
*
FROM
a1
INNER JOIN a2
ON
a1.id = a2.id
/

私の答え(予想)
Hash結合でINNER JOINかつ、WHERE句はないのでこの問題は簡単ですよね! (これは迷わないはず!!!)

Hash結合も外部表はデータセットの小さい方ですから...この場合だと、a2が外部表になるはず。

問題3
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
なお、D1とD2の多重度は、D1:D2 = 1:100
(個人的にUMLの多重度表記のほうが好きなので、UML表記の多重度で記述します。

表と索引
create table d1 (
id number
, data varchar2(1000)
) nologging
/
alter table d1 add constraint pk_d1 primary key (id) using index nologging
/
create table d2 (
id number not null
, seq# number not null
, data varchar2(1000)
) nologging
/
alter table d2 add constraint pk_d2 primary key (id, seq#) using index nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
D1 PK_D1 200 200 9
D2 PK_D2 20000 20000 870

SELECT
/* SQL03 */
/*+
MONITOR
*/
*
FROM
d2
WHERE
EXISTS (
SELECT
1
FROM
d1
WHERE
d1.id = d2.id
AND d1.id IN (1,5)
)
/


私の答え(予想)
これはちょっと意地悪な問題ですが、わかる人ならわかるはず。だと思って(信じて)作った問題です。 :)

最近のオプティマイザは、相関副問合せを可能であれば結合に書き換える(unnest)傾向が強いのをご存知でしょうか? 
となれば、方向はだいだい見えてきます。

d1.id IN (1,5)から最大で2件ヒットすると予想できますよね。
さらに、Nested Loop結合に書き換え、d2を内部表として結合できれば無駄がない。
つまり、駆動表は d1が理想的なはず。

d2を外部表にしてしまうと、WHERE条件がないので結合に置き換えてしまうとHash結合または、d2を全表走査して相関副問合せを都度実行のいづれかになってしまうでしょうね。(unnestの傾向が強いのでHash結合になる可能性が高そう)
ヒットするデータ件数から考えると、どちらも無駄なデータアクセスが多くなる可能性は高いのではないでしょうか。

問題4
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題3と同じです。)

SELECT
/* SQL04 */
/*+
MONITOR
*/
*
FROM
d1
INNER JOIN d2
ON
d1.id > d2.id
AND d2.id BETWEEN 1 AND 5
AND d2.seq# BETWEEN 2 AND 4
/

私の答え(予想)
結合条件が ">" であることに気付きましたか? ;)


これは、Nested Loop結合にも、Hash結合にもなりません。 Merge(sort/merge)結合が選択されます。

内部表は索引が利用できないのでソートが発生します。(ソートは避けられない)
外部表では索引を利用してソート処理が回避可能であれば、回避する。

つまりソート処理の影響を最小にしようとするはず、なので...

d1:d2=1:100という比率からd2のソート処理を重いと判断し回避するために外部表にするだろうなぁ〜〜。
私は、d2が外部表になるだろうと予想しています。
(直積じゃないMerge結合なんて最近お目にかかったことないのですが...)

余談)
Hash結合のない時代のOracleで、Merge結合の実行計画を見せられて、なんでソートしてるんですかね〜と、某ベンダーの方に質問されて一瞬固まったことを思い出したw


問題5
次に示すSQL文の駆動表(外部表)はどれでしょうか? 

表と索引定義
create table b1 (
id number
, data varchar2(1000)
) nologging
/
alter table b1 add constraint pk_b1 primary key(id) using index nologging
/
create table b3 (
id number
, seq# number
, data varchar2(1000)
) nologging
/
alter table b3 add constraint pk_b3 primary key (id, seq#) using index nologging
/
create table b2 (
id number
, seq# number
, subseq# number
, data varchar2(1000)
) nologging
/
alter table b2 add constraint pk_b2 primary key (id ,seq#, subseq#) using index nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
B1 PK_B1 20000 20000 870
B2 PK_B2 5000 5000 228
B3 PK_B3 500 500 23

ERDと多重度
b1 : b3 = 1 : 0..2
b3 : b2 = 1 : 0..10
(UML表記の多重度で記述しています。
20141221_120733

SELECT
/* SQL05 */
/*+
MONITOR
USE_HASH(b1 b3 b2)
*/
*
FROM
b1
INNER JOIN b3
ON
b1.id = b3.id
INNER JOIN b2
ON
b3.id = b2.id
AND b3.seq# = b2.seq#
/

私の答え(予想)
INNER JOINでHash結合かつ、WHERE句もないので簡単な問題ですよね。

外部表は、b3

ついでに、続けると、 b3とb2を結合すると最大で5000件、b3とb1を結合した場合、最大で500件なので、 結合順として理想的なのは、 b3, b1, b2 ですよね。


問題6
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題5と同じです。)

SELECT
/* SQL06 */
/*+
MONITOR
USE_HASH(b1 b3 b2)
*/
*
FROM
b1
LEFT OUTER JOIN b3
ON
b1.id = b3.id
LEFT OUTER JOIN b2
ON
b3.id = b2.id
AND b3.seq# = b2.seq#
/

私の答え(予想)
OUTER JOINなのですが、Hash結合なので、多分、予想通りにはならないだろうな〜〜と。

最近のオプティマイザは外部結合かつHash結合の場合、外部表と内部表をデータセットサイズに応じて入れ替える傾向が強いんですよね。PGAの使用サイズが小さくなるように....

とは言っても、机上の話なので、入れ替えないとしたらどうなるだろう..と、外部結合なので、内部表は、そのまま内部表として結合したほうがわかりやすいといえばわかりやすい。
(統計情報に乖離がある場合には入れ替えないほうが良い結果だったりすることもあります。)


注)()内を先に結合するものとします。
入れ替えなかった場合は、記述したままなので、(外部表、内部表)=外部表、内部表ということで、 (b1, b3), b2
データセットの小さい順に従えば、外部表と、内部表をいれかえて、b3, b1。
この結果セットの最大件数は元の外部表がb1ですから20,250件、b2が5,000件なので、また外部表と内部表を入れ替えて、結果として、b2, (b3, b1)ってことに...
(内部的に結合順が入れ替えられ、Right Joinに置き換えられることが多いんですよね〜いつ頃からだろう...10gあたりか。)

で、机上ならどちらを取るかということですが、机上では、ひとまず、SQL文の通りとしておくケースが多いです。あくまで私の場合ですよ。(難しいんですよ、これ。外れる可能性大w)
オプティマイザが内部的に外部表と内部表を入れ替えて問題がなければよし、問題があれば、内部的な入れ替えをヒントで止めるというのが私の基本方針なので、この辺りは、チューナーさんのさじ加減次第じゃないかなぁ、と思っています。

あはは、むずかし過ぎたか... 机上だと orz.

結合順がほぼ想定できる場合、状況次第で変わりそうな結合順、いろいろありますよね。
オプティマイザの気持ちになって考えてみると、いろいろ気づくことも多いんじゃないかなぁ。と思います。

オプティマイザってソートが嫌いなんだ〜、とか、PGA少ない方が好きなんだ〜とか....


役に立ったか、どうなのか不明なオチになりましたが.....

本年もよろしくお願いいたします。 m(_ _)m


あ、そうそう、一つ忘れてました。

次回は、Oracle Database 12c 12.1.0.2.0を使って試したオプティマイザが選択した駆動表(外部表)がどれだったかのか公開する予定です。


机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!!

| | コメント (0) | トラックバック (0)

2014年12月17日 (水)

机上SQLチューニング、クイズ! 駆動表(外部表)はどれだ!!!!

予備プラン発動中 :)

ということで、諸事情により開け忘れた窓を開けるためJPOUG Advent Calendar 2014 2回目の登場となりました。

JPOUG Advent Calendar 2014 17日目のエントリーです。


突然ですが、

「机上SQLチューーーーーニング、クイズ〜〜〜っ!!!!!!
 駆動表(外部表)はどれだ!!!!」


なお、このクイズには次の制限があります。

別途、本ブログで解答エントリーが公開(年明けを予定)されるまで、Oracle Databaseを利用して答え求めるのは禁止。 :-)
Oracle Databaseを利用せず、机上で、どの表を駆動表(外部表)にすれば理想的な実行計画になりそうか考えてみてね。

また、解答はOracle Database 12c Release 1 12.1.0.2.0 のオプティマイザをインストールしたまま(初期化パラメータはデフォルトのまま)の環境を使って行います。
(11gでも違わないと思いますが)

というクリスマスプレゼント :)

次に示されるSQL文の駆動表(外部表)はどれでしょうか? 

前提

統計情報と実データとの間に乖離はありません。
リテラル値で指定した検索条件に該当するデータは必ず存在します。


※引っ掛け問題もあるよ! :)


問題1
次に示すSQL文の駆動表(外部表)はどれでしょうか? 

表と索引
create table a1 (
id number primary key
, data varchar2(1000)
) nologging
/
create table a2 (
id number primary key
, data varchar2(1000)
) nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
A1 SYS_C0010377 10000 10000 295
A2 SYS_C0010378 2000 2000 59

SELECT
/* SQL01 */
/*+
MONITOR
USE_NL(a1 a2)
*/
*
FROM
a1
INNER JOIN a2
ON
a1.id = a2.id
WHERE
a1.id BETWEEN 1 AND 100
/

問題2
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題1と同じです。)

SELECT
/* SQL02 */
/*+
MONITOR
USE_HASH(a1 a2)
*/
*
FROM
a1
INNER JOIN a2
ON
a1.id = a2.id
/

問題3
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
なお、D1とD2の多重度は、D1:D2 = 1:100
(個人的にUMLの多重度表記のほうが好きなので、UML表記の多重度で記述します。

表と索引
create table d1 (
id number
, data varchar2(1000)
) nologging
/
alter table d1 add constraint pk_d1 primary key (id) using index nologging
/
create table d2 (
id number not null
, seq# number not null
, data varchar2(1000)
) nologging
/
alter table d2 add constraint pk_d2 primary key (id, seq#) using index nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
D1 PK_D1 200 200 9
D2 PK_D2 20000 20000 870

SELECT
/* SQL03 */
/*+
MONITOR
*/
*
FROM
d2
WHERE
EXISTS (
SELECT
1
FROM
d1
WHERE
d1.id = d2.id
AND d1.id IN (1,5)
)
/


問題4
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題3と同じです。)

SELECT
/* SQL04 */
/*+
MONITOR
*/
*
FROM
d1
INNER JOIN d2
ON
d1.id > d2.id
AND d2.id BETWEEN 1 AND 5
AND d2.seq# BETWEEN 2 AND 4
/


問題5
次に示すSQL文の駆動表(外部表)はどれでしょうか? 

表と索引定義
create table b1 (
id number
, data varchar2(1000)
) nologging
/
alter table b1 add constraint pk_b1 primary key(id) using index nologging
/
create table b3 (
id number
, seq# number
, data varchar2(1000)
) nologging
/
alter table b3 add constraint pk_b3 primary key (id, seq#) using index nologging
/
create table b2 (
id number
, seq# number
, subseq# number
, data varchar2(1000)
) nologging
/
alter table b2 add constraint pk_b2 primary key (id ,seq#, subseq#) using index nologging
/

統計情報
TABLE_NAME INDEX_NAME NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
-------------- -------------- ---------- ------------- -----------------
B1 PK_B1 20000 20000 870
B2 PK_B2 5000 5000 228
B3 PK_B3 500 500 23

ERDと多重度
b1 : b3 = 1 : 0..2
b3 : b2 = 1 : 0..10
(UML表記の多重度で記述しています。
20141221_120733

SELECT
/* SQL05 */
/*+
MONITOR
USE_HASH(b1 b3 b2)
*/
*
FROM
b1
INNER JOIN b3
ON
b1.id = b3.id
INNER JOIN b2
ON
b3.id = b2.id
AND b3.seq# = b2.seq#
/


問題6
次に示すSQL文の駆動表(外部表)はどれでしょうか? 
(表と索引、および、統計情報は問題5と同じです。)

SELECT
/* SQL06 */
/*+
MONITOR
USE_HASH(b1 b3 b2)
*/
*
FROM
b1
LEFT OUTER JOIN b3
ON
b1.id = b3.id
LEFT OUTER JOIN b2
ON
b3.id = b2.id
AND b3.seq# = b2.seq#
/


冬休みの宿題〜〜〜〜っ。暇つぶしにトライしてみてくださいね。(ニヤニヤ 

解答エントリーの公開は年明けを予定していま〜す。

We wish your merry christmas and a happy new year!

次の扉は、Tamie Yamamotoさんです。

| | コメント (0) | トラックバック (0)

2014年8月19日 (火)

pivotとSQL*PlusとSETコマンドと #2

昨日の続きですw

v$sys_time_modelの列データを行データへpivotで変換し、かつシェルで定期的に取得してみたものの、出力形式は今ひとつ。 iostatやvmstatのように出力したい。

前回のエントリーの出力結果は以下の通りでした。

[oracle ˜]$ ./sample.sh

SQL*Plus: Release 12.1.0.2.0 Production on 日 8月 17 21:10:47 2014

Copyright (c) 1982, 2014, Oracle. All rights reserved.


Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
に接続されました。
21:10:48 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66172667 7714783 127809578 27114874

経過: 00:00:00.02
21:10:48 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66179178 7720782 128085853 27160868

経過: 00:00:00.01
21:10:57 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66180864 7723782 128249102 27197862

経過: 00:00:00.00

じゃまな出力は、以下の通り

  • SQL*Plusのプロンプト
  • ヘッダー行
  • 経過時間
  • 出力の状態から見て、余分な改行

そして、不足している出力はメトリックのログ取得時のタイムスタンプ


以下のSQL*Plusシステム変数を調整追加すればなんとかなりそうな感じ。。。。

  • SQL*Plusのプロンプトは、 set sqlp "" で抑止。
  • ヘッダー行は、 set head off で抑止
  • 経過時間は、 set timi off で抑止
  • 余分な改行は、 たぶん、 set newp none で抑止
  • 直接関係ないけど、Excelにコピペするときにじゃまになるので set tab offでタブの混入抑止


不足しているログ取得時のタイムスタンプは、シェルのdateコマンドで取得した日時をSQL文に埋め込むことでなんとかなりそうな気がします。

と頭に浮かんだら忘れないうちに試してみますよ〜

★横に長くてごめんなさい。時間取れたらSyntaxHighlighterとか入れます詐欺 m(_ _)m

#!/bin/bash
#
(
echo "conn scott/tiger"
echo "set timi off time off tab off sqlp \"\" head off newp none"
echo "col db_time for 999999999999999999999999999999"
echo "col db_cpu for 999999999999999999999999999999"
echo "col bg_time for 999999999999999999999999999999"
echo "col bg_cpu for 999999999999999999999999999999"
while [ 1 ]
do
echo "SELECT db_time,db_cpu,bg_time,bg_cpu FROM (SELECT stat_name,value FROM v\$sys_time_model) PIVOT (MAX(value) FOR stat_name IN ('DB time' AS db_time,'DB CPU' AS db_cpu,'background elapsed time' AS bg_time,'background cpu time' AS bg_cpu));"
sleep 10
done
) | sqlplus /nolog


ん〜〜〜〜〜、なんか、惜しい!!!!  いい感じにななったのに。... しばし考える。。。。

[oracle ˜]$ ./sample.sh

SQL*Plus: Release 12.1.0.2.0 Production on 日 8月 17 21:45:43 2014

Copyright (c) 1982, 2014, Oracle. All rights reserved.

21:45:43 > 接続されました。
21:45:44 SCOTT> 67126005 8476651 182158980 37408311

67129138 8479651 182310474 37445305


きた〜〜〜、神が降りてきたのでちょっと書き換えた

#!/bin/bash
#
(
echo "conn scott/tiger"
echo "set timi off time off sqlp \"\""
echo "col db_time for 999999999999999999999999999999"
echo "col db_cpu for 999999999999999999999999999999"
echo "col bg_time for 999999999999999999999999999999"
echo "col bg_cpu for 999999999999999999999999999999"
while [ 1 ]
do
t=`date +'%DT%T'`
echo "SELECT SUBSTR('${t}',1,INSTR('${t}','T')-1) as logged_date,SUBSTR('${t}',INSTR('${t}','T')+1) as logged_time,db_time,db_cpu,bg_time,bg_cpu FROM (SELECT stat_name,value FROM v\$sys_time_model) PIVOT (MAX(value) FOR stat_name IN ('DB time' AS db_time,'DB CPU' AS db_cpu,'background elapsed time' AS bg_time,'background cpu time' AS bg_cpu));"
echo "set head off newp none"
sleep 10
done
) | sqlplus /nolog


ん〜〜〜、まだ余計な改行というか空行がある。。。なんだこれ。。。。再び、考え中........ あ、あれだ! 出力行数を返すやつ!

SQL*Plus: Release 12.1.0.2.0 Production on 日 8月 17 21:56:21 2014

Copyright (c) 1982, 2014, Oracle. All rights reserved.

21:56:21 > 接続されました。
21:56:22 SCOTT>
LOGGED_D LOGGED_T DB_TIME DB_CPU BG_TIME BG_CPU
-------- -------- ------------------------------- ------------------------------- ------------------------------- -------------------------------
08/17/14 21:56:21 67688748 8937570 198751230 40650823

08/17/14 21:56:31 67695983 8945569 199012673 40699815


で、できたのがこれ。

#!/bin/bash
#
(
echo "conn scott/tiger"
echo "set timi off time off sqlp \"\" feed off"
echo "col db_time for 999999999999999999999999999999"
echo "col db_cpu for 999999999999999999999999999999"
echo "col bg_time for 999999999999999999999999999999"
echo "col bg_cpu for 999999999999999999999999999999"
while [ 1 ]
do
t=`date +'%DT%T'`
echo "SELECT SUBSTR('${t}',1,INSTR('${t}','T')-1) as logged_date,SUBSTR('${t}',INSTR('${t}','T')+1) as logged_time,db_time,db_cpu,bg_time,bg_cpu FROM (SELECT stat_name,value FROM v\$sys_time_model) PIVOT (MAX(value) FOR stat_name IN ('DB time' AS db_time,'DB CPU' AS db_cpu,'background elapsed time' AS bg_time,'background cpu time' AS bg_cpu));"
echo "set head off newp none"
sleep 10
done

) | sqlplus /nolog

出力結果は以下のようになり、 iostatやvmstat風にヘッダー行は一度だけ、その後、定期的に取得されるメトリックが出力されていくイメージに! :)

[oracle ˜]$ ./sample.sh

SQL*Plus: Release 12.1.0.2.0 Production on 日 8月 17 22:02:59 2014

Copyright (c) 1982, 2014, Oracle. All rights reserved.

22:02:59 > 接続されました。
22:02:59 SCOTT>
LOGGED_D LOGGED_T DB_TIME DB_CPU BG_TIME BG_CPU
-------- -------- ------------------------------- ------------------------------- ------------------------------- -------------------------------
08/17/14 22:02:59 68026469 9252517 211057107 42641516
08/17/14 22:03:09 68034074 9261515 211217278 42686507
08/17/14 22:03:19 68039874 9267514 211414489 42730501
08/17/14 22:03:29 68045221 9272514 211576132 42769498
08/17/14 22:03:39 68051396 9276513 211844118 42862480
^C
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing optionsとの接続が切断されました。


Enjoy!


pivotとSQL*PlusとSETコマンドと

| | コメント (0) | トラックバック (0)

2014年8月18日 (月)

pivotとSQL*PlusとSETコマンドと

Oracle Databaseの性能試験で以下のようなメトリックを定期取得して、分析やビジュアライズに利用している方も多いと思います。(思ってます。)
でも、ですねぇ。
以下のv$sys_time_model動的パフォーマンスビューも典型例なのですが、列持ちのメトリックが多いので集計にはかなり苦労します。というか、してます。

v$sys_time_model動的パフォーマンスビューの出力例)

SCOTT> SELECT stat_name, value from v$sys_time_model;

STAT_NAME VALUE
---------------------------------------------------------------- ----------
DB time 64073868
DB CPU 6549986
background elapsed time 42988476
background cpu time 11080311

・・・以下略・・・

列持ちなんですよね、 列持ち!(しつこいw

行持ちにしたいですよね。 どう料理しましょう。 まさか、手作業ではやってないですよね。

SQL文でやってますよね! 私もそうです。
ちなみに、UNION連打はしてませんからね!(キリっ!

昔はほかに手がなかったのですが、Oracle11gから便利で比較的読みやすい構文がサポートされています。

列持ちを行持ちにするといえば....そうです、あれです。 pivot

ということで、

pivotを使って、v$sys_time_modelを例にオレオレv$sys_time_modelを作り出してみます。
(これができれば、数あるオラクルの動的パフォーマンスビューをもっと好きになれるんじゃないかなぁ。と思います。)

では、早速

v$sys_time_modelの stat_name列の列値が、'DB time'、'DB CPU'、 'background elapsed time'、'background cpu time'の4つのメトリックを列持ちから行持ちに変え、オレオレv$sys_time_model作り出すSQL文です。
ビューは作りませんけど (^^;;;

SELECT 
db_time
,db_cpu
,bg_time
,bg_cpu
FROM
(
SELECT
stat_name
,value
FROM
v$sys_time_model
)
PIVOT
(
MAX(value)
FOR stat_name IN
(
'DB time' AS db_time
,'DB CPU' AS db_cpu
,'background elapsed time' AS bg_time
,'background cpu time' AS bg_cpu
)
)
;


これを実行すると以下のような結果になります。本来4行なのですが、1行にできるんです! 便利ですね。 pivot (pivotの逆の操作をする unpivotもあります)

   DB_TIME     DB_CPU    BG_TIME     BG_CPU
---------- ---------- ---------- ----------
63895360 6364016 32965031 8718671

いい感じになってきました。


しかし、まだ物足りないですよね。 そう!
取得時のタイムスタンプとか、例えば iostat や vmstatのように定期的に取得したくなってきます!!!!

「門外不出のOracle現場ワザ」 第5章 DBアクセスの空白地帯 コネクションプーリングを極めるの定期的にSQLを発行するシェルを作成するには? でも解説されているのでこの方法で取得されている方も多いと思います。:)

ただ、そのまんまだと以下のような出力になってしまいます。 iostatやvmstatの出力をイメージしちゃうと余分な表示が多いわけです。

[oracle ˜]$ ./sample.sh

SQL*Plus: Release 12.1.0.2.0 Production on 日 8月 17 21:10:47 2014

Copyright (c) 1982, 2014, Oracle. All rights reserved.


Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
に接続されました。
21:10:48 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66172667 7714783 127809578 27114874

経過: 00:00:00.02
21:10:48 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66179178 7720782 128085853 27160868

経過: 00:00:00.01
21:10:57 SCOTT>
DB_TIME DB_CPU BG_TIME BG_CPU
------------------------------- ------------------------------- ------------------------------- -------------------------------
66180864 7723782 128249102 27197862

経過: 00:00:00.00

前述の出力は以下のコードで取得したのですが、実はそんなに手を加えなくても vmstatやiostatのような出力形式で、みなさんの大好きなExcelで集計しやすくすることができるんですよ。
どこを変更すればよいか分かった人、手を挙げて〜〜〜〜っ!

注)scottユーザにselect any dictionaryシステム権限付けてます。

#!/bin/bash
#
(
echo "conn scott/tiger"
echo "set timi on time on"
echo "col db_time for 999999999999999999999999999999"
echo "col db_cpu for 999999999999999999999999999999"
echo "col bg_time for 999999999999999999999999999999"
echo "col bg_cpu for 999999999999999999999999999999"
while [ 1 ]
do
echo "SELECT db_time,db_cpu,bg_time,bg_cpu FROM (SELECT stat_name,value FROM v\$sys_time_model) PIVOT (MAX(value) FOR stat_name IN ('DB time' AS db_time,'DB CPU' AS db_cpu,'background elapsed time' AS bg_time,'background cpu time' AS bg_cpu));"
sleep 10
done
) | sqlplus /nolog


つづきは、次のエントリーで :)

| | コメント (0) | トラックバック (0)

2014年7月30日 (水)

Oracle Database 12c R1 12.1.0.2.0 In-memory option はじめの一歩。

こまけーことは置いといて、
Oracle Database 12c R1 12.1.0.2.0 In-memory optionが利用できるようになったので簡単に試してみた。


環境は以下の通り

Oracle VM VirtualBox 4.3.12 for OS X
Oracle Linux 6.4
Oracle Database 12c EE 12.1.0.2.0 for Linux x86-64


時間が無かったので、マニュアルを斜め読みしつつ、とにかくインストール....
inmemory系初期化パラメータの確認。 (12.1.0.1と12.1.0.2の初期化パラメータの差分確認はあとで:TODO)

inmemory_sizeパラメータが0なのでin-memory column storeは機能しない状態がデフォルトなのか..

Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
に接続されました。
SYS>
SYS> show parameter inmemory

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
inmemory_clause_default string
inmemory_force string DEFAULT
inmemory_max_populate_servers integer 0
inmemory_query string ENABLE
inmemory_size big integer 0
inmemory_trickle_repopulate_servers_ integer 1
percent
optimizer_inmemory_aware boolean TRUE
SYS>

以下の表を作成してあります。 10万行のうちb='1'である行は10行だけにしてあります。

SCOTT> desc foobar
名前 NULL? 型
----------------------------------------- -------- ----------------------------
A NUMBER
B CHAR(1)


SCOTT> select count(1) from foobar;

COUNT(1)
----------
100000


今まで通りに表を作成すると、in-memory column storeはdisabledで作成されるのか! ふむふむ。

SCOTT> select table_name,inmemory from user_tables where table_name='FOOBAR';

TABLE_NAME INMEMORY
------------------------------ --------
FOOBAR DISABLED

索引は作成していないので以下のようなクエリを実行すれば、TABLE ACCESS FULLですよね。

SCOTT> select /*+ gather_plan_statistics */ count(1) from foobar where b = '1';

COUNT(1)
----------
10

SCOTT> @show_actual_plan

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
SQL_ID 4zurd70pg2qna, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(1) from foobar where b = '1'

Plan hash value: 2479556450

---------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 191 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 191 |
|* 2 | TABLE ACCESS FULL| FOOBAR | 1 | 15 | 10 |00:00:00.01 | 191 |
---------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("B"='1')

Note
-----
- dynamic statistics used: dynamic sampling (level=2)


23行が選択されました。


in-memory column storeを利用するための準備〜〜〜。
alter table文でin-memory column storeを有効化。(簡単だな!)

SCOTT> alter table foobar inmemory;

表が変更されました。


おっと、忘れてました、 inmemory_sizeパラメータを設定しないといけないんだった...

ちなみに、inmemory_sizeパラメータは静的パラメータなので、scope=spfileを付けることをお忘れなく...

SYS> alter system set inmemory_size = 10m;
alter system set inmemory_size = 10m
*
行1でエラーが発生しました。:
ORA-02097: 指定した値が無効なので、パラメータを変更できません。 ORA-02095:
指定した初期化パラメータを変更できません。


メモリ関係のパラメータ変えて起動しなくなるって過去やらかしたことがあるので、手が勝ってに動くw

SYS> create pfile from spfile;

ファイルが作成されました。

変更できたました。
SYS> alter system set inmemory_size = 10m scope=spfile;

システムが変更されました。

SYS>

....中略....

起動してみましょう! あらららら。

SYS> startup
ORA-64353: in-memory area size cannot be less than 100MB

ううううう、なんということでしょう! w このパラメータ間違うと起動しないのな!!!!! 注意しないと。 > 俺


in-memory初期化パラメータは 100MB以上に設定しましょうね。マニュアルにも書いてますから。(読みましょうね。 (^^;;;;

気を取り直して、再チャレンジ :)

[oracle@emperor ˜]$ sqlplus / as sysdba

SQL*Plus: Release 12.1.0.2.0 Production on 火 7月 29 06:34:46 2014

Copyright (c) 1982, 2014, Oracle. All rights reserved.

アイドル・インスタンスに接続しました。

SYS> create spfile from pfile;

ファイルが作成されました。

SYS> startup
ORACLEインスタンスが起動しました。

Total System Global Area 3875536896 bytes
Fixed Size 3717856 bytes
Variable Size 2248148256 bytes
Database Buffers 1610612736 bytes
Redo Buffers 13058048 bytes
データベースがマウントされました。
データベースがオープンされました。

SYS> alter system set inmemory_size = 100m scope=spfile;

システムが変更されました。

SYS> shutdown
データベースがクローズされました。
データベースがディスマウントされました。
ORACLEインスタンスがシャットダウンされました。


SYS> startup
ORACLEインスタンスが起動しました。

Total System Global Area 3875536896 bytes
Fixed Size 3717856 bytes
Variable Size 2231371040 bytes
Database Buffers 1509949440 bytes
Redo Buffers 13058048 bytes
In-Memory Area 117440512 bytes
データベースがマウントされました。
データベースがオープンされました。
SYS>
SYS>


In-Memory Areaとリストされています。
うまくいったようなので in-memory column storeを試してみます!!!!

SCOTT> select /*+ gather_plan_statistics */ count(1) from foobar where b = '1';

COUNT(1)
----------
10

SCOTT>
SCOTT>
SCOTT> @show_actual_plan

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------
SQL_ID 4zurd70pg2qna, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(1) from foobar where b = '1'

Plan hash value: 2479556450

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 8 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 8 |
|* 2 | TABLE ACCESS INMEMORY FULL| FOOBAR | 1 | 15 | 10 |00:00:00.01 | 8 |
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - inmemory("B"='1')
filter("B"='1')

Note
-----
- dynamic statistics used: dynamic sampling (level=2)


24行が選択されました。


in-memory column storeを利用しない場合、Buffer Getsが191ありましたが、in-memory column storeを利用した場合、Buffer Getsが8と大幅に減少しています!!!。データにもよるだろうけど、このケースだと、4%程度になってますね。

KEEP乗せとか手動でやらなくて済むケースあるんだろうな〜と、遠くを眺めながら.......in-memory column store初めの一歩はここまで。


つづく。

| | コメント (0) | トラックバック (0)

2014年7月26日 (土)

TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #3

つづきです。
バッファキャッシュにヒットした場合はどうかというと、物理I/Oは発生しないので、そのまんまの結果ですよね。(^^;;;

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 195 0.11 0.18 0 6287 0 2907
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 197 0.11 0.18 0 6287 0 2907

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 110 (SCOTT)
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
2907 2907 2907 NESTED LOOPS (cr=6287 pr=0 pw=0 time=206651 us)
2907 2907 2907 NESTED LOOPS (cr=3380 pr=0 pw=0 time=116870 us cost=5362 size=1744812 card=2851)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID BATCHED HIGH_CLUSTERING_FACTOR (cr=3108 pr=0 pw=0 time=42395 us cost=2861 size=872406 card=2851)
2907 2907 2907 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=201 pr=0 pw=0 time=12292 us cost=8 size=0 card=2851)(object id 93727)
2907 2907 2907 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=272 pr=0 pw=0 time=36902 us cost=0 size=0 card=1)(object id 93725)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2907 pr=0 pw=0 time=37280 us cost=1 size=306 card=1)


Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT MODE: ALL_ROWS
2907 NESTED LOOPS
2907 NESTED LOOPS
2907 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID BATCHED) OF 'HIGH_CLUSTERING_FACTOR' (TABLE)
2907 INDEX MODE: ANALYZED (RANGE SCAN) OF 'PK_HIGH_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'PK_LOW_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'LOW_CLUSTERING_FACTOR' (TABLE)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 195 0.00 0.00
SQL*Net message from client 195 0.00 0.15
SQL*Net more data to client 193 0.00 0.01
********************************************************************************

12cで、optimizer_features_enable='11.2.0.1'にしてバッファキャッシュをクリアすれば、11gと同じ実行計画(TABLE ACCESS BY INDEX ROWID)になって、db file parallel readになるよね!

という確認もしておいた!

call     count       cpu    elapsed       disk      query    current        rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.42 1.59 0 0 0 0
Execute 1 0.00 0.01 0 0 0 0
Fetch 195 18.34 61.02 2450 6287 0 2907
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 197 18.77 62.63 2450 6287 0 2907

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 110 (SCOTT)
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
2907 2907 2907 NESTED LOOPS (cr=6287 pr=2450 pw=0 time=92881090 us)
2907 2907 2907 NESTED LOOPS (cr=3380 pr=2232 pw=0 time=59488238 us cost=5362 size=1744812 card=2851)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=3108 pr=2221 pw=0 time=31142410 us cost=2861 size=872406 card=2851)
2907 2907 2907 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=201 pr=7 pw=0 time=3493710 us cost=8 size=0 card=2851)(object id 93727)
2907 2907 2907 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=272 pr=11 pw=0 time=10377721 us cost=0 size=0 card=1)(object id 93725)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2907 pr=218 pw=0 time=13380036 us cost=1 size=306 card=1)


Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT MODE: ALL_ROWS
2907 NESTED LOOPS
2907 NESTED LOOPS
2907 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID BATCHED) OF 'HIGH_CLUSTERING_FACTOR' (TABLE)
2907 INDEX MODE: ANALYZED (RANGE SCAN) OF 'PK_HIGH_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'PK_LOW_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'LOW_CLUSTERING_FACTOR' (TABLE)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 195 0.00 0.21
Disk file operations I/O 1 0.00 0.00
db file sequential read 452 0.21 1.54
SQL*Net message from client 195 15.35 16.71
SQL*Net more data to client 193 0.00 0.47
db file parallel read 190 0.05 5.91
********************************************************************************


整形前SQLトレースより抜粋 (db file parallel readが索引で現れている

WAIT #140513423347200: nam='db file parallel read' ela= 16615 files=1 blocks=13 requests=13 obj#=93726 tim=6258017207

straceより抜粋...io_submit()、だよね〜。:)

io_submit(140513465466880, 13, ...中略... ) = 13


最後に、11g R2 11.2.0.1で同じことを確認(確認するまでもないんだけどね)

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 169 0.58 6.88 2148 5439 0 2509
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 171 0.58 6.88 2148 5439 0 2509

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 84 (SCOTT)

Rows Row Source Operation
------- ---------------------------------------------------
2509 NESTED LOOPS (cr=5439 pr=2148 pw=0 time=19292592 us)
2509 NESTED LOOPS (cr=2930 pr=1931 pw=0 time=24524478 us cost=5012 size=1530612 card=2501)
2509 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2683 pr=1922 pw=0 time=24392808 us cost=2510 size=765612 card=2502)
2509 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=174 pr=5 pw=0 time=11616 us cost=8 size=0 card=2502)(object id 113222)
2509 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=247 pr=9 pw=0 time=0 us cost=0 size=0 card=1)(object id 113220)
2509 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2509 pr=217 pw=0 time=0 us cost=1 size=306 card=1)


Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT MODE: ALL_ROWS
2509 NESTED LOOPS
2509 NESTED LOOPS
2509 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'HIGH_CLUSTERING_FACTOR' (TABLE)
2509 INDEX MODE: ANALYZED (RANGE SCAN) OF 'PK_HIGH_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2509 INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'PK_LOW_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2509 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'LOW_CLUSTERING_FACTOR' (TABLE)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 169 0.00 0.00
SQL*Net message from client 169 24.57 25.54
Disk file operations I/O 1 0.00 0.00
db file parallel read 167 0.09 5.66
db file sequential read 357 0.01 0.92
SQL*Net more data to client 167 0.00 0.01
********************************************************************************

11gまでは、実行計画上には現れず実行時に内部的に行っていたI/O最適化の動きが、12cからは実行計画上でも確認できるようになったというのは分かり易くていい。
時間あったら、v$sesstatも載せるかも。 :)

さて、12c 12.1.0.2.0のダウンロードも終わったので、別の楽しみが増えましたよね、みなさん!

ダウンロード! しましたか〜〜〜〜〜っ! :)

みなさんの、12c ネタ祭りを楽しみにしております。 (^^


TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #1
TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #2

| | コメント (0) | トラックバック (0)

2014年7月25日 (金)

TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #2

Oracle Database 12c R1 12.1.0.2.0 EEが公開されましたが、12.1.0.1.0で試してあります。

注)バッファキャッシュをクリア後にSQL文を実行してあります。

SELECT
/*+
leading(t2 t1)
use_nl(t2 t1)
index(t2 pk_high_clustering_factor)
*/
t2.id
,t2.name
,t1.name
FROM
low_clustering_factor t1
INNER JOIN high_clustering_factor t2
ON
t1.id = t2.id
WHERE
t2.id BETWEEN 30001 AND 35000

上記のSQL文でTABLE ACCESS BY INDEX ROWID BATCHED操作を行わせてみた。

以下、SQLトレースの結果です。予想通り db file parallel readが発生しています。
高いクラスタリングファクタを持つ索引をアクセス、不連続なROWDをかき集めかき集めたROWIDをある程度まとめて1度のI/Oリクエストでバッファキャッシュへ読み込む動作ですよね!

call     count       cpu    elapsed       disk      query    current        rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.07 6 6 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 195 0.48 4.92 2288 6338 0 2907
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 197 0.50 4.99 2294 6344 0 2907

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 110 (SCOTT)
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
2907 2907 2907 NESTED LOOPS (cr=6338 pr=2288 pw=0 time=2150320 us)
2907 2907 2907 NESTED LOOPS (cr=3431 pr=2087 pw=0 time=6036546 us cost=3368 size=1159620 card=3514)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID BATCHED HIGH_CLUSTERING_FACTOR (cr=3107 pr=2082 pw=0 time=5948676 us cost=1012 size=579810 card=3514)
2907 2907 2907 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=201 pr=0 pw=0 time=15435 us cost=11 size=0 card=3514)(object id 93727)
2907 2907 2907 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=324 pr=5 pw=0 time=56981 us cost=0 size=0 card=1)(object id 93725)
2907 2907 2907 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2907 pr=201 pw=0 time=312848 us cost=1 size=165 card=1)


Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT MODE: ALL_ROWS
2907 NESTED LOOPS
2907 NESTED LOOPS
2907 TABLE ACCESS (BY INDEX ROWID BATCHED) OF 'HIGH_CLUSTERING_FACTOR' (TABLE)
2907 INDEX (RANGE SCAN) OF 'PK_HIGH_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 INDEX (UNIQUE SCAN) OF 'PK_LOW_CLUSTERING_FACTOR' (INDEX (UNIQUE))
2907 TABLE ACCESS (BY INDEX ROWID) OF 'LOW_CLUSTERING_FACTOR' (TABLE)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 195 0.00 0.00
db file sequential read 209 0.03 0.32
SQL*Net message from client 195 19.95 21.21
db file parallel read 194 0.09 4.33
SQL*Net more data to client 193 0.00 0.01
********************************************************************************

SQLトレース(整形前)のトレースログより : db file parallel read (この時は、最小 3blocks〜15blocksの範囲で行われていた。

WAIT #139657829206792: nam='db file parallel read' ela= 64710 files=1 blocks=12 requests=12 obj#=93726 tim=4037078687

ちなみにstraceでみるとio_submitが呼び出されていて11gの頃と変わりはなかった :)

つまり、実行計画上、db file parallel readやるからね!! 物理I/Oを行うときは! という意思が明確に表示されるようになった。 
分かり易くなっていい!!!!
これだと物理I/Oがあれば、db file parallel readやってるな〜と実行計画を見ただけで想像できますな:)




TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #1

| | コメント (0) | トラックバック (0)

2014年7月23日 (水)

TABLE ACCESS BY INDEX ROWID BATCHED (Oracle Database 12c R1) ってなに! #1

Oracle® Database SQLチューニング・ガイド 12cリリース1(12.1) - ROWIDによる表アクセス: 例にも記載され、昨年試していたときにも現れていた、table access by index rowid batched という操作って、実際のところdb file parallel readをやるよ! と実行計画に表すようになったということだよね! それだけだよね。

ということを確認してみた。(TODOと書いてから1年経過してしまった orz )

詳細はあした以降に書きまする。 :)

20140723_220538

| | コメント (0) | トラックバック (0)

2014年6月28日 (土)

InterSystems Symposia 2014に行ってきた

db tech showcase 2014 Osakaより先だったのだけど...
InterSystems Symposia 2014に行ってきた。という話。

CachéはSQLでアクセスする必要はないのだけど、RDBMSからの乗り換え組でSQLから乗り換えコストの低減目的で導入されたインターフェースだというのは間違いなし、
昔からM言語をやってきた人達からすれば、SQLアクセスだと! はぁ〜? 的な感じも無くはないし、おまけ的なイメージは強いんだろうな。
その分のオーバーヘッドもあるしな〜。 と思ってたのですが....

最近の動きを見ているとSQLアクセスにも本気なのかな。と思わせる様子があるんだよね。

Caché SQL 最新情報の資料P3部分でダイレクトアクセス(元からあるNoSQLな部分)とSQLアクセスのメリット、デメリットの解説やビットマップインデックスのメンテナンス機能。

20140628_124018
Caché SQL 最新情報のセッション資料は上のリンクからダウンロードできますよ!


そして、 Push Subquery Conditions into UNION LegsなどのSQL実行計画最適化、OracleのUSE_CONCAT相当など多数のヒント、パラレルクエリなど"おまけのインターフェース"とは思えなくなってきた感がある。


ちなみに、Caché自体はスパース多次元配列なので行指向、列指向のようなことはないんだけど、列指向的な加工もできちゃうわけで、それはそれで面白いかなと、勝ってに妄想してみたりw

たまには、右脳を活性化しないとね、イメージ力大事 :)

| | コメント (0) | トラックバック (0)

2014年6月13日 (金)

SQL Developer 4の素敵なコマンドライン de SQL整形 :)

Version 4.0.1ではWindowsでも同じ問題がでていたようですね。以下OTNフォーラムにも投稿されていたようです。
OS X版 Version 4.0.1でも同じだったので不具合だったようです。
https://community.oracle.com/thread/3562219


Version 4.0.2がリリースされたので改善されたか確かめてみました。 OS X版のSQLDeveloper 4.0.2 で!

OTN-JPではまだ、4.0.1のようですが、USでは 4.0.2のようです。OTN-JPでもそのうち4.0.2になるのでしょうね :)
以下はUSへリンクです。
http://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/index.html

20140613_45308


OS X版のsdcliを実行するには実行権を付与する必要があります。sdcliは以下のパスにあります。かなり深いところにあります!
(OS X版は以下深いところにあるので、SQLDeveloperのアイコン上で右クリック>コンテクストメニュー>パッケージの内容を表示から辿ってパスを探してもいいですし、以下のパス直接コピペでも行けます。)

20140613_45551

20140613_45613


discus-mother:˜ oracle$ cd /Applications/SQLDeveloper.app/Contents/Resources/sqldeveloper/sqldeveloper/bin
discus-mother:bin oracle$ chmod +x sdcli
discus-mother:bin oracle$ ll
total 672
-rw-r--r--@ 1 oracle staff 49279 5 3 02:48 SQLDeveloperIcons.icns
-rw-r--r--@ 1 oracle staff 0 5 3 02:48 jdk.conf
-rw-r--r--@ 1 oracle staff 440 5 3 02:48 logging-debug.conf
-rw-r--r--@ 1 oracle staff 363 5 3 02:48 logging.conf
-rwxrwxr-x@ 1 oracle staff 586 5 3 02:50 sdcli
-rw-r--r--@ 1 oracle staff 99 5 3 02:50 sdcli-Darwin.conf
-rw-r--r--@ 1 oracle staff 446 5 3 02:50 sdcli.boot
-rw-r--r--@ 1 oracle staff 854 5 3 02:50 sdcli.conf
-rw-r--r--@ 1 oracle staff 71205 5 3 02:48 splash.gif
-rw-r--r--@ 1 oracle staff 71205 5 3 02:48 splash.png
-r-xr-xr-x@ 1 oracle staff 3220 5 3 02:48 sqldeveloper
-rw-r--r--@ 1 oracle staff 415 5 3 02:48 sqldeveloper-Darwin.conf
-rw-r--r--@ 1 oracle staff 661 5 3 02:48 sqldeveloper-debug.conf
-rw-r--r--@ 1 oracle staff 204 5 3 02:48 sqldeveloper-nondebug.conf
-rw-r--r--@ 1 oracle staff 445 5 3 02:48 sqldeveloper.bat
-rw-r--r--@ 1 oracle staff 340 5 3 02:48 sqldeveloper.boot
-rw-r--r--@ 1 oracle staff 1175 5 3 02:48 sqldeveloper.conf
-rw-r--r--@ 1 oracle staff 83456 5 3 02:48 sqldeveloper64W.exe
-rw-r--r--@ 1 oracle staff 131 5 3 02:54 version.properties
discus-mother:bin oracle$


パスは通っていないので設定する必要があります。

discus-mother:˜ oracle$ cat .bashrc
alias ll='ls -lv'

#for SQLDeveloper sdcli
export PATH=$PATH:/Applications/SQLDeveloper.app/Contents/Resources/sqldeveloper/sqldeveloper/bin

discus-mother:˜ oracle$


Version 4.0.1では以下の状況でExceptionが発生していましたが、Version 4.0.2では解決してます!!! 

discus-mother:˜ oracle$ sdcli

Oracle SQL Developer
Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.

使用可能な機能:
cart: データベース・カート・バッチ・タスク
dba: 基本バッチDBAタスク
format: SQL Format Task
migration: Database Migration Tasks
reports: 基本バッチ・レポート・タスク
unittest: ユニット・テスト・バッチ・タスク
discus-mother:˜ oracle$

ということで、
前から試したかった SQL文の整形機能。 GUIだとSQLワークシートでSQL文整形機能が提供されているためデータベースへ接続必要だったんですよね。
機能としては素敵だったのですが、データベース接続が行えない環境では使えない機能だったんですよ。
そんな環境で読むに耐えない巨大で未整形なSQL文を渡され涙目だった日々を思い出しますw

コマンドラインならデータベース接続不要なのがいいっす!

discus-mother:˜ oracle$ sdcli format

Oracle SQL Developer
Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.

format input=<入力ファイルまたはディレクトリ> output=<出力ファイルまたはディレクトリ>
成功しました。
discus-mother:˜ oracle$


以下のようなSQL文を整形してみます。

discus-mother:˜ oracle$ cat sample_unformated.sql
select a.id,a.name,b.location,b.phone_no from foo a innter join bar b on a.id = b.id order by a.name;


意外と地味な感じの処理終了メッセージですが、整形できたようです。整形結果を確認!

discus-mother:˜ oracle$ sdcli format input=sample_unformated.sql output=sample_formated.sql

Oracle SQL Developer
Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.

成功しました。
discus-mother:˜ oracle$


いいですね〜 :) 最後の改行がないのですは、ご愛嬌ということで.

discus-mother:˜ oracle$ cat sample_formated.sql
SELECT a.id,
a.name,
b.location,
b.phone_no
FROM foo a innter
JOIN bar b
ON a.id = b.id
ORDER BY a.name;discus-mother:˜ oracle$
discus-mother:˜ oracle$

では、超便利な一括整形のテスト。 以下のような未整形なSQLファイルを含むディレクトリと整形後SQLファイルを格納するディレクトリを用意して....

discus-mother:˜ oracle$ mkdir unformated_sqls
discus-mother:˜ oracle$ mkdir formated_sqls
discus-mother:˜ oracle$ dir=unformated_sqls; for fname in `ls $dir`; do echo -e \\n\\nfile : $fname; cat $dir/$fname; done;


file : sample1.sql
select a.id,a.name,b.location,b.phone_no from foo a innter join bar b on a.id = b.id order by a.name;


file : sample2.sql
select a.id,a.name,b.location,b.phone_no from foo a innter join bar b on a.id = b.id order by a.name;


一括整形の場合はディレクトリ名のみ指定すればOK!
おおおおおおおおお〜〜〜〜、できた〜〜〜〜SQL文の一括整形!!!  素敵!!

discus-mother:˜ oracle$ sdcli format input=unformated_sqls output=formated_sqls

Oracle SQL Developer
Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.

成功しました。


かくに〜〜〜〜〜ん!!

discus-mother:˜ oracle$ dir=formated_sqls; for fname in `ls $dir`; do echo -e \\n\\nfile : $fname; cat $dir/$fname; done;


file : sample1.sql
SELECT a.id,
a.name,
b.location,
b.phone_no
FROM foo a innter
JOIN bar b
ON a.id = b.id
ORDER BY a.name;

file : sample2.sql
SELECT a.id,
a.name,
b.location,
b.phone_no
FROM foo a innter
JOIN bar b
ON a.id = b.id
ORDER BY a.name;


Good job!!!!! SQL Developer Team!


| | コメント (0) | トラックバック (0)

2014年5月24日 (土)

シーケンスでパーティションだと〜っ! (Oracle Database 12c R1ではドキュメントに記載なし...だが...) - おまけ

シーケンスでパーティションだと〜っ! (Oracle Database 12c R1ではドキュメントに記載なし...だが...) を書いた後に、もしかして、パーティション表でIdentity columnを使うと勝ってにパーティショニングされたシーケンスが自動生成されたりしないのかな〜っ。

と思って念のために試してみた。


結果は見ての通り....

次のリリースではなにかあるのかもね....。

SCOTT@nonpdb12c> select * from v$version;

BANNER CON_ID
-------------------------------------------------------------------------------- ----------
Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production 0
PL/SQL Release 12.1.0.1.0 - Production 0
CORE 12.1.0.1.0 Production 0
TNS for Linux: Version 12.1.0.1.0 - Production 0
NLSRTL Version 12.1.0.1.0 - Production 0


以下のように、id列をIdentity columnにし、そのid列をパーティショニングキーにしたハッシュパーティション表を作成してみた。

SCOTT@nonpdb12c> r
1 select
2 identity_column
3 ,column_name
4 ,data_type
5 ,data_default
6 from
7 user_tab_columns
8 where
9 table_name = 'HASH_PART_TABLE_USING_ID_COL'
10* order by column_id

IDE COLUMN_NAME DATA_TYPE DATA_DEFAULT
--- ------------------------------ -------------------- ------------------------------
YES ID NUMBER "SCOTT"."ISEQ$$_93596".nextval
NO DATA VARCHAR2

SCOTT@nonpdb12c> r
1 select
2 table_name
3 ,partitioning_type
4 ,partition_count
5 ,partitioning_key_count
6 from
7 user_part_tables
8 where
9* table_name = 'HASH_PART_TABLE_USING_ID_COL'

TABLE_NAME PARTITION PARTITION_COUNT PARTITIONING_KEY_COUNT
------------------------------ --------- --------------- ----------------------
HASH_PART_TABLE_USING_ID_COL HASH 4 1

SCOTT@nonpdb12c> r
1 select
2 name
3 ,object_type
4 ,column_name
5 from
6 user_part_key_columns
7 where
8* name = 'HASH_PART_TABLE_USING_ID_COL'

NAME OBJEC COLUMN_NAME
------------------------------ ----- ------------------------------
HASH_PART_TABLE_USING_ID_COL TABLE ID

対応するシーケンスはどうかというと、PARTITION_COUNTはNULLのまま....ですね。あははは。

SCOTT@nonpdb12c> select * from user_sequences where sequence_name = 'ISEQ$$_93596';

SEQUENCE_NAME MIN_VALUE MAX_VALUE INCREMENT_BY C O CACHE_SIZE LAST_NUMBER PARTITION_COUNT S K
------------------------------ ---------- --------------------------------- ------------ - - ---------- ----------- --------------- - -
ISEQ$$_93596 1 9999999999999999999999999999 1 N N 20 1 N N

| | コメント (0) | トラックバック (0)

2014年4月30日 (水)

シーケンスでパーティションだと〜っ! (Oracle Database 12c R1ではドキュメントに記載なし...だが...)

Identity Columnがどのシーケンスを利用しているかを確認するディクショナリビューを調べていて見つけた、シーケンスのパーティション化!
とはいっても現時点ではマニュアルには解説はないので使い方は不明なままですが、RACとかで便利な感じとか、単にパラレル処理でいい感じになるのかとか、いろいろ想像を巡らしております。はい。

SCOTT@nonpdb12c> break on table_name skip 1
SCOTT@nonpdb12c> select table_name,has_identity from user_tables where has_identity='YES';

TABLE_NAME HAS_IDENTITY
------------------------------ ------------
TABLE_FOOBAR_IDENTITY2 YES

TABLE_FOOBAR_IDENTITY YES


SCOTT@nonpdb12c> select * from user_tab_identity_cols;

TABLE_NAME COLUMN_NAM GENERATION IDENTITY_OPTIONS
------------------------------ ---------- ---------- --------------------------------------------------------------------------------------------------------------------------------------------
TABLE_FOOBAR_IDENTITY ID BY DEFAULT START WITH: 1, INCREMENT BY: 1, MAX_VALUE: 9999999999999999999999999999, MIN_VALUE: 1, CYCLE_FLAG: N, CACHE_SIZE: 20, ORDER_FLAG: N

TABLE_FOOBAR_IDENTITY2 ID ALWAYS START WITH: 1, INCREMENT BY: 1, MAX_VALUE: 9999999999999999999999999999, MIN_VALUE: 1, CYCLE_FLAG: N, CACHE_SIZE: 20, ORDER_FLAG: N


SCOTT@nonpdb12c> select table_name,column_name,data_default,identity_column from user_tab_columns where identity_column='YES';

TABLE_NAME COLUMN_NAM DATA_DEFAULT IDENTITY_COLUMN
------------------------------ ---------- -------------------------------------------------------------------------------- ---------------
TABLE_FOOBAR_IDENTITY ID "SCOTT"."ISEQ$$_93570".nextval YES

TABLE_FOOBAR_IDENTITY2 ID "SCOTT"."ISEQ$$_93589".nextval YES


SCOTT@nonpdb12c> select * from user_sequences;

SEQUENCE_NAME MIN_VALUE MAX_VALUE INCREMENT_BY C O CACHE_SIZE LAST_NUMBER PARTITION_COUNT S K
------------------------------ ---------- ---------- ------------ - - ---------- ----------- --------------- - -
ISEQ$$_93570 1 1.0000E+28 1 N N 20 101 N N
ISEQ$$_93589 1 1.0000E+28 1 N N 20 101 N N


ん? PARTITION_COUNTだと? シーケンスでパーティションだと〜〜〜〜〜っ!


Oracle Database 12c R1のマニュアルの構文解説にもまったく記載されていないが..........
Oracle® Database SQL言語リファレンス 12cリリース1 (12.1) CREATE SEQUENCE

SCOTT@nonpdb12c> create sequence p_seq start with 1 maxvalue 9999999 partition 4;

順序が作成されました。

おおおおおおおおおおおおおお〜〜〜できたw Partitioned Sequence!!!!!!

SCOTT@nonpdb12c> select * from user_sequences;

SEQUENCE_NAME MIN_VALUE MAX_VALUE INCREMENT_BY C O CACHE_SIZE LAST_NUMBER PARTITION_COUNT S K
------------------------------ ---------- ---------- ------------ - - ---------- ----------- --------------- - -
ISEQ$$_93570 1 1.0000E+28 1 N N 20 101 N N
ISEQ$$_93589 1 1.0000E+28 1 N N 20 101 N N
P_SEQ 1 9999999 1 N N 20 1 4 N N


RACで効果がありそうな機能だが....、そもそも、どう使うんだ、謎。

次のリリースではなにか使えるのか...現リリースでも裏で使われているのか......

参考
dbi services Blog:Oracle Partitioned Sequences - a future new feature in 12c?

| | コメント (0) | トラックバック (0)

2014年4月29日 (火)

Oracle Database 12cで実装されたidentity columnのメモ

Oracle® Database SQL言語リファレンス 12cリリース1 (12.1) CREATE TABLE identity_clause


制限事項に、「CREATE TABLE AS SELECTを使用すると、列に対するIDのプロパティが継承されなくなります。」と記載されているのでかるく確認 :)

SCOTT@nonpdb12c> CREATE TABLE table_foobar_identity2
2 (
3 id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY
4 ,data VARCHAR2(10)
5 );

表が作成されました。

SCOTT@nonpdb12c> create table table_foobar_identity2_tmp as select * from table_foobar_identity2;

表が作成されました。

SCOTT@nonpdb12c> break on table_name skip 1
SCOTT@nonpdb12c> select table_name,column_name,identity_column from user_tab_columns where table_name like 'TABLE_FOOBAR%' order by table_name,column_id;

TABLE_NAME COLUMN_NAME IDE
------------------------------ ------------------------------ ---
TABLE_FOOBAR_IDENTITY2 ID YES
DATA NO

TABLE_FOOBAR_IDENTITY2_TMP ID NO
DATA NO

CREATE TABLE AS SELECTで復籍してもidentity columnは継承されてない。ふむふむ。


同マニュアルの注意には、「ID列を作成するときには、パフォーマンスを向上するために、デフォルトの20よりも大きな値を使用してCACHE句を指定してください。」
とある.
シーケンスを利用しているので注意するところは一緒ということね。

SCOTT@nonpdb12c> select * from user_sequences;

SEQUENCE_NAME MIN_VALUE MAX_VALUE INCREMENT_BY C O CACHE_SIZE LAST_NUMBER PARTITION_COUNT S K
------------------------------ ---------- ---------- ------------ - - ---------- ----------- --------------- - -
ISEQ$$_93570 1 1.0000E+28 1 N N 20 1 N N

参考
ORACLE-BASE:Identity Columns in Oracle Database 12c Release 1 (12.1)
Inside Oracle – Julian Dyke:Oracle 12c New Feature – Identity Columns

| | コメント (0) | トラックバック (0)

2014年1月 6日 (月)

SELECT ~ FOR UPDATE SKIP LOCKED その4 - もしもITL不足だったら...

skip lockedのつづきです。

ITLエントリー不足時のskip lockedの動きを確認してみるか....


100行が1ブロックに収まるような表を作成しておく...計算上、ITLエントリーは最大で4エントリー程度になるように....したつもり....
(ASSMで、INITRANSはデフォルト、ブロックサイズは8KB、PCTFREEは0%)

SCOTT@pdborcl> r
1 select
2 objectid
3 ,file#
4 ,block#
5 ,count(id) as num_of_rows
6 from
7 (
8 select
9 dbms_rowid.rowid_object(rowid) as objectid
10 ,dbms_rowid.rowid_relative_fno(rowid) as file#
11 ,dbms_rowid.rowid_block_number(rowid) as block#
12 ,id
13 from
14 q
15 )
16 group by
17 objectid
18 ,file#
19 ,block#
20 order by
21 objectid
22 ,file#
23* ,block#

OBJECTID FILE# BLOCK# NUM_OF_ROWS
---------- ---------- ---------- -----------
93077 9 972461 100

※セッション1 - lockできた

SESSION1@pdborcl> select * from q where id = '0001' for update;

ID
----
TEXT_STRING
----------------------------------------------------------------------
0001
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

※セッション2 - lockできた。 この時点で 1 + 1 = 2 のITLエントリは使い切っている。

SESSION2@pdborcl> select * from q where id = '0002' for update;

ID
----
TEXT_STRING
---------------------------------------------------------------------
0002
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

※セッション3 - lockできた。 ITLがブロック内の空きスペースに作れたため :)

SESSION3@pdborcl> select * from q where id = '0003' for update;

ID
----
TEXT_STRING
---------------------------------------------------------------------
0003
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

※セッション4 - 計算上のITLエントリー数の上限値. 問題なくlockできた。

SESSION4@pdborcl> select * from q where id = '0004' for update;

ID
----
TEXT_STRING
---------------------------------------------------------------------
0004
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

※セッション5 - 狙い通り! 5つめのITLエントリーを作成するだけの空きがブロック内にない状況なので待機しちゃう :)

SESSION5@pdborcl> select * from q where id = '0005' for update;


待機イベント見れば一目瞭然、ITLエントリー不足で待機してますね!

SYS@pdborcl> select username,event from v$session where username = 'SCOTT'

USERNAME EVENT
---------- ----------------------------------------
SCOTT SQL*Net message from client
SCOTT SQL*Net message from client
SCOTT enq: TX - allocate ITL entry
SCOTT SQL*Net message from client
SCOTT SQL*Net message from client

ここまでは、ITL不足な状況のfor update文ではよく見かける光景ですよね :)


for update skip lockedにすると.....

※セッション1 - locked!

SESSION1@pdborcl> select * from q where id = '0001' for update skip locked;

ID
----
TEXT_STRING
---------------------------------------------------------------------
0001
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

※セッション2 - locked!!

SESSION2@pdborcl> select * from q where id = '0002' for update skip locked;

ID
----
TEXT_STRING
---------------------------------------------------------------------
0002
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

※セッション3 - locked!!!

SESSON3@pdborcl> select * from q where id = '0003' for update skip locked;

ID
----
TEXT_STRING
---------------------------------------------------------------------
0003
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

※セッション4 - locked!!!!

SESSION4@pdborcl> select * from q where id = '0004' for update skip locked;

ID
----
TEXT_STRING
---------------------------------------------------------------------
0004
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

※セッション5(ITLエントリーが確保できず id = '0005'の行をlockすることができないので空振りします。興味深い動きですよね。)

SESSION5@pdborcl> select * from q where id = '0005' for update skip locked;

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


次回へつづく.....かもしれない。



SELECT ~ FOR UPDATE SKIP LOCKED その1 - @sh2ndさんエントリの復習など
SELECT ~ FOR UPDATE SKIP LOCKED その2
SELECT ~ FOR UPDATE SKIP LOCKED その3

| | コメント (0) | トラックバック (0)

2014年1月 5日 (日)

SELECT ~ FOR UPDATE SKIP LOCKED その3

skip lockedのつづきです。

skip lockedの特徴を確認しておきますか!

skip lockedはロックの獲得ができた行だけをロック待機なしで返してくれる :)
IDが'0001', '0002', '0003', '0005'の行をロックしておく。

SESSION1@pdborcl> select id from q where id in ('0001', '0002', '0003', '0005') for update skip locked;

ID
----
0001
0002
0003
0005

IDが '0001','0002','0003'の行は先行のトランザクションで既にロックされているため、ロックできた行だけがロック待機なしで返される。

SESSION2@pdborcl> select id from q where id in ('0001', '0002', '0003', '0004', '0005', '0006') for update skip locked;

ID
----
0004
0006

もちろん、ロックできる行が1行もなければロックを待機せず空振!

SESSION3@pdborcl> select id from q where id in ('0001', '0002', '0003') for update skip locked;

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


つづく。


SELECT ~ FOR UPDATE SKIP LOCKED その1 - @sh2ndさんエントリの復習など
SELECT ~ FOR UPDATE SKIP LOCKED その2

| | コメント (0) | トラックバック (0)

2013年12月31日 (火)

SELECT ~ FOR UPDATE SKIP LOCKED その2

つづきです。


そういえば、skip lockedって構文がマニュアルでは解説されていないリリースでふつーーーーに、使われててビビったことあったな。
(マニュアルに書かれていないリリースでは、自己責任使ってねw)


Oracle12c R1 - SKIP LOCKED
http://docs.oracle.com/cd/E49329_01/server.121/b71278/statements_10002.htm#SQLRF55374


Oracle11g R2 - (skip locked、推奨事項などが追記された)
http://docs.oracle.com/cd/E16338_01/server.112/b56299/statements_10002.htm#i2126016


Oracle11g R1 - (skip locked登場)
http://otndnld.oracle.co.jp/document/products/oracle11g/111/doc_dvd/server.111/E05750-03/statements_10.htm#7292


Oracle10g R2 (ここまでのマニュアルにはskip lookedは記載されていない。AQがらみで使われていたのは確かだ....)
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/server.102/B19201-02/statements_10.html#6799


どのあたりのバージョンから登場していたのかはJonathan Lewisさんのブログからたどれば分かると思うよ...
http://jonathanlewis.wordpress.com/2010/05/31/skip-locked/


Oracle11g R2のドキュメントからは以下のような追記されている。


「この機能は、Oracle Streams Advanced Queuingなどのマルチコンシューマ・キュー環境で使用するために設計されています。
 キュー・コンシューマは、他のコンシューマによってロックされた行はスキップして未ロックの行を取得できるので、
 他のコンシューマの操作が終了するまで待つ必要はなくなります。
 SKIP LOCKED機能を直接使用するかわりに、Oracle Streams Advanced Queuing APIを使用することをお薦めします。」

と。

以下のリリースで確認したが skip lockedに関して動作の差は無いようだ。
2013/1/5追記
動作の差は無いようだ、とは書いたが、結果オーライってことでございます。内部動作までは見えないので(キリつ

・Oracle11g R1 11.1.0.7.0
・Oracle11g R2 11.2.0.1.0
・Oracle11g R2 11.2.0.2.0
・Oracle12c R1 12.1.0.1.0


※セッション1
SESSION1> select * from q order by id;

ID DATA
---------- ----------
1 a
2 b
3 c

※セッション1
SESSION1> SELECT id, data FROM q WHERE id = (SELECT MIN(id) FROM q) FOR UPDATE SKIP LOCKED;

ID DATA
---------- ----------
1 a

※セッション2
SKIP LOCKEDなのでしようとしていたレコードがロックできなければ空振りし、ロック獲得を待機しません
SESSION2> SELECT id, data FROM q WHERE id = (SELECT MIN(id) FROM q) FOR UPDATE SKIP LOCKED;

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


※セッション1
SESSION1> DELETE FROM q WHERE id = 1;

1行が削除されました。

※セッション2
何度やっても同じですよね :)
SESSION2> SELECT id, data FROM q WHERE id = (SELECT MIN(id) FROM q) FOR UPDATE SKIP LOCKED;

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


※セッション1
SESSION1> COMMIT;

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

SESSION1>

※セッション2
ここで初めて、対象レコードがロックできます!
マルチコンシューマ・キュー環境向けって意味がよ〜〜〜〜く分かる動きだと思います
SESSION2> SELECT id, data FROM q WHERE id = (SELECT MIN(id) FROM q) FOR UPDATE SKIP LOCKED;

ID DATA
---------- ----------
2 b


skip lockedって面白いよね。

skip lockedの特徴について、もう少し書く予定、多分、来年へつづく。。。。。


みなさま、良いお年を!



SELECT ~ FOR UPDATE SKIP LOCKED その1 - @sh2ndさんエントリの復習など

| | コメント (0) | トラックバック (0)

SELECT ~ FOR UPDATE SKIP LOCKED その1 - @sh2ndさんのエントリの復習など

JPOUT Advent Calender 2013の@sh2ndさんのエントリーが面白かったので大晦日に酒飲みながら... :)
まずは twitterでのやり取りなど....


20131231_113305


20131231_163205

イケテナイとか、良いとか、自由に言えるのはユーザーだからこそだと思うんだ。
セールストークじゃない生の情報って大切だ。


ということで、skip locked へ行く前に、Oracle11g R1 11.1.0.7.0 および、Oracle12c R1 12.1.0.1.0 で @sh2ndさんのエントリーの復習から :)

※セッション1
SESSION1> select * from q order by id;

ID DATA
---------- ----------
1 a
2 b
3 c

※セッション1
SESSION1> SELECT id, data FROM q WHERE id = (SELECT MIN(id) FROM q) FOR UPDATE;

ID DATA
---------- ----------
1 a

※セッション2
SESSION2> SELECT id, data FROM q WHERE id = (SELECT MIN(id) FROM q) FOR UPDATE;
(ここで待たされる。待つことは正しい動き)

※セッション1
SESSION1> DELETE FROM q WHERE id = 1;

1行が削除されました。

※セッション1
SESSION1> commit;

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

SESSION1>

※セッション2はここでロックを獲得できる。
ID DATA
---------- ----------
2 b

SESSION2>


Oracle11g R2 11.2.0.1.0とOracle11g R2 11.2.0.2.0は、Wrong Resultだったんだと思うが...

※セッション1
SESSION1> select * from q order by id;

ID DATA
---------- ----------
1 a
2 b
3 c

※セッション1
SESSION1> SELECT id, data FROM q WHERE id = (SELECT MIN(id) FROM q) FOR UPDATE;

ID DATA
---------- ----------
1 a

※セッション2
SESSION2> SELECT id, data FROM q WHERE id = (SELECT MIN(id) FROM q) FOR UPDATE;
(ここで待たされる。というところまでは同じ)


※セッション1
SESSION1> DELETE FROM q WHERE id = 1;

1行が削除されました。

※セッション1
SESSION1> COMMIT;

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

SESSION1>

※セッション2 (注1
(しか〜〜〜し、なんとレコードが選択されませんでした!!!!!)
レコードが選択されませんでした。

SESSION2>

結果
OracleREAD COMMITTED
11.1.0.7.0ID=2を取得
11.2.0.1.0空振り
11.2.0.2.0空振り
12.1.0.1.0ID=2を取得


注1)
Oracle11g 11.2.0.3.0以降では修正されているとのこと...(私は手持ちのが無かったので未確認...だれか書いてw)

SELECT ~ FOR UPDATE SKIP LOCKEDへつづく...

| | コメント (0) | トラックバック (0)

2013年7月 8日 (月)

rownum使って満足しちゃってると.....おまけのおまけ FETCH FIRST N ROWS ONLY編

20130628_233812


Oracle Database 12c R1 12.1.0.1.0 がリリースされたので、自分用のメモとしても使うネタから :)


実行環境は Oracle Database 12c R1 12.1.0.1.0 EE/Oracle Linux 6.4/VirtualBox for OS X 11g R2と同じデータをインポートし統計情報を再取得しています。


まず、rownum から!

おおおおお〜、 まあ想定通りではあるのですが、11gとは違う点が一つ。

TABLE ACCESS BY INDEX ROWID BATCHEDと見慣れないオペレーションが...興味深いですね。これ :)

別途調べるTODO :)

23:14:41 SCOTT@pdborcl> r
1 select /*+ leading(tc) use_nl(tc tb ta) */ *
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 join ta
8 on
9 tb.version = ta.version
10 where
11 tc.version between 1 and 10
12* and rownum <= 1000

1000行が選択されました。

経過: 00:00:00.13

実行計画
----------------------------------------------------------
Plan hash value: 1463769645

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 2952K| 4813 (1)| 00:00:01 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 2002 | 5910K| 4813 (1)| 00:00:01 |
| 4 | NESTED LOOPS | | 4004 | 7894K| 2810 (1)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TC | 4004 | 3953K| 585 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | PK_TC | | | 14 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID | TB | 1 | 1008 | 1 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | PK_TB | 1 | | 0 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | PK_TA | 1 | | 0 (0)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID | TA | 1 | 1004 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(ROWNUM<=1000)
6 - access("TC"."VERSION">=1 AND "TC"."VERSION"<=10)
8 - access("TC"."VERSION"="TB"."VERSION" AND "TC"."BRANCH#"="TB"."BRANCH#")
filter("TB"."VERSION"<=10 AND "TB"."VERSION">=1)
9 - access("TB"."VERSION"="TA"."VERSION")
filter("TA"."VERSION">=1 AND "TA"."VERSION"<=10)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2418 consistent gets
0 physical reads
0 redo size
1059870 bytes sent via SQL*Net to client
1270 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000 rows processed


次に、Oracle Database 12c R1 12.1.0.1.0 から実装された FETCH FIRST N ROWS ONLYとの比較

FETCH FIRST N ROWS ONLYを利用した場合TCを全表走査してしまったので、rownum利用時と同じオブジェクト参照させるためヒントでPK_TC索引を利用するようチューニングしてあります
ただ..なぜか、TABLE ACCESS BY INDEX ROWID BATCHEDではなくTABLE ACCESS BY INDEX ROWIDとなっています。


さらに不思議なことに、COSTが異常に跳ね上がっています。Rowsの見積もり件数で大きな差があることが影響しているように見えるんだが、なんでこんなに違うんだろう!
いろいろ試してみると、不思議なことがおきそうな気配w


Buffer Getsはほぼ同じなので、全表走査になりやすい?場合、索引が利用できそうなら使わせたほうがいいですな。多分。

23:12:22 SCOTT@pdborcl> r
1 select /*+ leading(tc) use_nl(tc tb ta) index(tc pk_tc) */ *
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 join ta
8 on
9 tb.version = ta.version
10 where
11 tc.version between 1 and 10
12* fetch first 1000 rows only

1000行が選択されました。

経過: 00:00:00.13

実行計画
----------------------------------------------------------
Plan hash value: 3242622317

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 300K| 456M| 1141K (1)| 00:00:45 |
|* 1 | VIEW | | 300K| 456M| 1141K (1)| 00:00:45 |
|* 2 | WINDOW NOSORT STOPKEY | | 300K| 864M| 1141K (1)| 00:00:45 |
| 3 | NESTED LOOPS | | | | | |
| 4 | NESTED LOOPS | | 300K| 864M| 1141K (1)| 00:00:45 |
| 5 | NESTED LOOPS | | 600K| 1155M| 841K (1)| 00:00:33 |
| 6 | TABLE ACCESS BY INDEX ROWID| TC | 1200K| 1156M| 174K (1)| 00:00:07 |
|* 7 | INDEX RANGE SCAN | PK_TC | 1200K| | 3278 (1)| 00:00:01 |
| 8 | TABLE ACCESS BY INDEX ROWID| TB | 1 | 1008 | 1 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | PK_TB | 1 | | 0 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | PK_TA | 1 | | 0 (0)| 00:00:01 |
| 11 | TABLE ACCESS BY INDEX ROWID | TA | 1 | 1004 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("from$_subquery$_006"."rowlimit_$$_rownumber"<=1000)
2 - filter(ROW_NUMBER() OVER ( ORDER BY NULL )<=1000)
7 - access("TC"."VERSION">=1 AND "TC"."VERSION"<=10)
9 - access("TC"."VERSION"="TB"."VERSION" AND "TC"."BRANCH#"="TB"."BRANCH#")
filter("TB"."VERSION"<=10 AND "TB"."VERSION">=1)
10 - access("TB"."VERSION"="TA"."VERSION")
filter("TA"."VERSION">=1 AND "TA"."VERSION"<=10)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2420 consistent gets
0 physical reads
0 redo size
1044954 bytes sent via SQL*Net to client
1270 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000 rows processed

FETCH FISRSTを利用すると、rownumとは異なり、内部でROW_NUMBER() OVER( OVER BY NULL) <= N
のような変換が発生するんですね!!!! 


rownumの弱点はどうなったか確認してみます。

12c でも rownum は rownumですね! という確認から。

23:37:32 SCOTT@pdborcl> r
1 select /*+ leading(tc) use_nl(tc tb ta) */ *
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 join ta
8 on
9 tb.version = ta.version
10 where
11 tc.version between 11 and 20
12* and rownum <= 1000

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

経過: 00:00:08.21

実行計画
----------------------------------------------------------
Plan hash value: 1463769645

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 2952K| 37940 (1)| 00:00:02 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 1101 | 3250K| 37940 (1)| 00:00:02 |
| 4 | NESTED LOOPS | | 22019 | 42M| 26928 (1)| 00:00:02 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TC | 38230 | 36M| 5683 (1)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | PK_TC | | | 109 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID | TB | 1 | 1008 | 1 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | PK_TB | 1 | | 0 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | PK_TA | 1 | | 0 (0)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID | TA | 1 | 1004 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(ROWNUM<=1000)
6 - access("TC"."VERSION">=11 AND "TC"."VERSION"<=20)
8 - access("TC"."VERSION"="TB"."VERSION" AND "TC"."BRANCH#"="TB"."BRANCH#")
filter("TB"."VERSION">=11 AND "TB"."VERSION"<=20)
9 - access("TB"."VERSION"="TA"."VERSION")
filter("TA"."VERSION">=11 AND "TA"."VERSION"<=20)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
1174942 consistent gets
176197 physical reads
0 redo size
913 bytes sent via SQL*Net to client
533 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed


FETCH FIRST N ROWS ONLYでも弱点は同じです!

23:36:02 SCOTT@pdborcl> r
1 select /*+ leading(tc) use_nl(tc tb ta) index(tc pk_tc) */ *
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 join ta
8 on
9 tb.version = ta.version
10 where
11 tc.version between 11 and 20
12* fetch first 1000 rows only

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

経過: 00:00:07.36

実行計画
----------------------------------------------------------
Plan hash value: 3242622317

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 29999 | 45M| 1141K (1)| 00:00:45 |
|* 1 | VIEW | | 29999 | 45M| 1141K (1)| 00:00:45 |
|* 2 | WINDOW NOSORT STOPKEY | | 29999 | 86M| 1141K (1)| 00:00:45 |
| 3 | NESTED LOOPS | | | | | |
| 4 | NESTED LOOPS | | 29999 | 86M| 1141K (1)| 00:00:45 |
| 5 | NESTED LOOPS | | 599K| 1155M| 841K (1)| 00:00:33 |
| 6 | TABLE ACCESS BY INDEX ROWID| TC | 1200K| 1156M| 174K (1)| 00:00:07 |
|* 7 | INDEX RANGE SCAN | PK_TC | 1200K| | 3278 (1)| 00:00:01 |
| 8 | TABLE ACCESS BY INDEX ROWID| TB | 1 | 1008 | 1 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | PK_TB | 1 | | 0 (0)| 00:00:01 |
|* 10 | INDEX UNIQUE SCAN | PK_TA | 1 | | 0 (0)| 00:00:01 |
| 11 | TABLE ACCESS BY INDEX ROWID | TA | 1 | 1004 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("from$_subquery$_006"."rowlimit_$$_rownumber"<=1000)
2 - filter(ROW_NUMBER() OVER ( ORDER BY NULL )<=1000)
7 - access("TC"."VERSION">=11 AND "TC"."VERSION"<=20)
9 - access("TC"."VERSION"="TB"."VERSION" AND "TC"."BRANCH#"="TB"."BRANCH#")
filter("TB"."VERSION">=11 AND "TB"."VERSION"<=20)
10 - access("TB"."VERSION"="TA"."VERSION")
filter("TA"."VERSION">=11 AND "TA"."VERSION"<=20)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
1174942 consistent gets
175116 physical reads
0 redo size
913 bytes sent via SQL*Net to client
533 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed


最後に、ハッシュ結合にした場合のrownum vs fetch first n rows onlyの比較

NL結合にすべきか、ハッシュ結合にすべきかどうかは、悩むんですよ。状況に合わせて使い分ける必要があるのは、11gでも12cでも同じだと思います。理由は前回のエントリを参照のこと。

rownumでハッシュ結合にした場合、11g R2では黙っていても索引を利用していたのですが、12c R1では全表走査になる傾向が見られます。
11gの実行計画と同じオブジェクトを参照するようにヒントでチューニングしてありますが、やはりここでもTABLE ACCESS BY INDEX ROWID BATCHEDが行われていますね。

実に興味深い。

23:46:06 SCOTT@pdborcl> r
1 select /*+ leading(tc) use_hash(tc tb ta) index(ta pk_ta) */ *
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 join ta
8 on
9 tb.version = ta.version
10 where
11 tc.version between 1 and 10
12* and rownum <= 1000

1000行が選択されました。

経過: 00:00:34.63

実行計画
----------------------------------------------------------
Plan hash value: 3316229909

--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 4923K| | 199K (1)| 00:00:08 |
|* 1 | COUNT STOPKEY | | | | | | |
|* 2 | HASH JOIN | | 2002 | 9857K| | 199K (1)| 00:00:08 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED | TA | 10 | 10040 | | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | PK_TA | 10 | | | 1 (0)| 00:00:01 |
|* 5 | HASH JOIN | | 4004 | 7894K| 1170M| 199K (1)| 00:00:08 |
|* 6 | TABLE ACCESS FULL | TC | 1200K| 1156M| | 139K (1)| 00:00:06 |
| 7 | TABLE ACCESS BY INDEX ROWID BATCHED| TB | 10001 | 9844K| | 1486 (0)| 00:00:01 |
|* 8 | INDEX RANGE SCAN | PK_TB | 10000 | | | 26 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(ROWNUM<=1000)
2 - access("TB"."VERSION"="TA"."VERSION")
4 - access("TA"."VERSION">=1 AND "TA"."VERSION"<=10)
5 - access("TC"."VERSION"="TB"."VERSION" AND "TC"."BRANCH#"="TB"."BRANCH#")
6 - filter("TC"."VERSION"<=10 AND "TC"."VERSION">=1)
8 - access("TB"."VERSION">=1 AND "TB"."VERSION"<=10)


統計
----------------------------------------------------------
1258 recursive calls
0 db block gets
515770 consistent gets
516798 physical reads
0 redo size
1062679 bytes sent via SQL*Net to client
1270 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000 rows processed

FETCH FIRST N ROWS ONLYを利用した場合

00:20:36 SCOTT@pdborcl> r
1 select /*+ leading(tc) use_hash(tc tb ta) index(ta pk_ta) index(tb pk_tb) */ *
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 join ta
8 on
9 tb.version = ta.version
10 where
11 tc.version between 1 and 10
12* fetch first 1000 rows only

1000行が選択されました。

経過: 00:00:32.69

実行計画
----------------------------------------------------------
Plan hash value: 3430218324

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 300K| 456M| | 199K (1)| 00:00:08 |
|* 1 | VIEW | | 300K| 456M| | 199K (1)| 00:00:08 |
|* 2 | WINDOW NOSORT STOPKEY | | 300K| 864M| | 199K (1)| 00:00:08 |
|* 3 | HASH JOIN | | 300K| 864M| | 199K (1)| 00:00:08 |
| 4 | TABLE ACCESS BY INDEX ROWID | TA | 10 | 10040 | | 4 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | PK_TA | 10 | | | 1 (0)| 00:00:01 |
|* 6 | HASH JOIN | | 600K| 1155M| 1170M| 199K (1)| 00:00:08 |
|* 7 | TABLE ACCESS FULL | TC | 1200K| 1156M| | 139K (1)| 00:00:06 |
| 8 | TABLE ACCESS BY INDEX ROWID| TB | 10001 | 9844K| | 1486 (0)| 00:00:01 |
|* 9 | INDEX RANGE SCAN | PK_TB | 10000 | | | 26 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("from$_subquery$_006"."rowlimit_$$_rownumber"<=1000)
2 - filter(ROW_NUMBER() OVER ( ORDER BY NULL )<=1000)
3 - access("TB"."VERSION"="TA"."VERSION")
5 - access("TA"."VERSION">=1 AND "TA"."VERSION"<=10)
6 - access("TC"."VERSION"="TB"."VERSION" AND "TC"."BRANCH#"="TB"."BRANCH#")
7 - filter("TC"."VERSION"<=10 AND "TC"."VERSION">=1)
9 - access("TB"."VERSION">=1 AND "TB"."VERSION"<=10)


統計
----------------------------------------------------------
1456 recursive calls
0 db block gets
516181 consistent gets
514506 physical reads
0 redo size
1045990 bytes sent via SQL*Net to client
1270 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
75 sorts (memory)
0 sorts (disk)
1000 rows processed


Top N に関してはrownumでも fetch first N rows onlyでも弱点は弱点なのでrownumと同じ考え方でチューニングの方向を決めないと、ね。


rownum使って満足しちゃってると..... #1
rownum使って満足しちゃってると..... #2
rownum使って満足しちゃってると..... #3
rownum使って満足しちゃってると..... #4
rownum使って満足しちゃってると.....おまけ

| | コメント (0) | トラックバック (0)

2013年6月23日 (日)

rownum使って満足しちゃってると.....おまけ

rownumを使っているチューニングしました! (キリっ

と、しているのに以下の例ようにハッシュ結合が行われ残念な結果になっていることってありませんか? (私はよく目にします (@@)

rownumの弱点を説明している実行計画をよーーーく見返してください。 ハッシュ結合にはしていません

なぜだか分かりますか?

11:14:44 SCOTT> l
1 select *
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 join ta
8 on
9 tb.version = ta.version
10 where
11 tc.version between 1 and 10
12* and rownum <= 1000
11:14:44 SCOTT> /

1000行が選択されました。

経過: 00:00:59.08

実行計画
----------------------------------------------------------
Plan hash value: 2217783216

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 4923K| | 201K (1)| 00:40:14 |
|* 1 | COUNT STOPKEY | | | | | | |
|* 2 | HASH JOIN | | 1001 | 4928K| | 201K (1)| 00:40:14 |
| 3 | TABLE ACCESS BY INDEX ROWID | TA | 10 | 10040 | | 3 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | PK_TA | 10 | | | 1 (0)| 00:00:01 |
|* 5 | HASH JOIN | | 2002 | 3947K| 1161M| 201K (1)| 00:40:14 |
|* 6 | TABLE ACCESS FULL | TC | 1190K| 1148M| | 141K (1)| 00:28:20 |
| 7 | TABLE ACCESS BY INDEX ROWID| TB | 10001 | 9844K| | 1456 (0)| 00:00:18 |
|* 8 | INDEX RANGE SCAN | PK_TB | | | | 26 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(ROWNUM<=1000)
2 - access("TB"."VERSION"="TA"."VERSION")
4 - access("TA"."VERSION">=1 AND "TA"."VERSION"<=10)
5 - access("TC"."VERSION"="TB"."VERSION" AND "TC"."BRANCH#"="TB"."BRANCH#")
6 - filter("TC"."VERSION"<=10 AND "TC"."VERSION">=1)
8 - access("TB"."VERSION">=1 AND "TB"."VERSION"<=10)


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
514382 consistent gets
514341 physical reads
0 redo size
1050353 bytes sent via SQL*Net to client
1245 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000 rows processed

ハッシュ結合などは典型的なのですが、操作を始めるとその操作が終了するまで次の操作に移行しないんですよ。

上記の例では赤い部分のハッシュ結合が終了するまでCOUNT STOPKEY操作(Id=1の部分)つまり、rownumの効果が発揮できないわけです。(残念!


結合がありrownumを使って行数制限している場合には、”大抵の場合" Nested Loop結合にしたほうがいいんですよ! OLTP系のようにレスポンスを重視する傾向が強い場合は... (弱点もありますが...

レスポンス重視かスループット重視か、オプティマイザが実行計画を組み立てる際に影響をパラメータがありますよね、  レースカーのフロントウイングやりやウィングのように立てたり寝かしたりするようなパラメータが.... :)

話がそれてしまいましたが...w

11:13:28 SCOTT> l
1 select /*+ leading(tc) use_nl(tc tb ta) */ *
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 join ta
8 on
9 tb.version = ta.version
10 where
11 tc.version between 1 and 10
12* and rownum <= 1000
11:13:29 SCOTT> /

1000行が選択されました。

経過: 00:00:00.29

実行計画
----------------------------------------------------------
Plan hash value: 2389411418

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 2952K| 3798 (1)| 00:00:46 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 1001 | 2955K| 3798 (1)| 00:00:46 |
| 4 | NESTED LOOPS | | 2002 | 3947K| 2796 (1)| 00:00:34 |
| 5 | TABLE ACCESS BY INDEX ROWID| TC | 4004 | 3953K| 571 (0)| 00:00:07 |
|* 6 | INDEX RANGE SCAN | PK_TC | | | 13 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID| TB | 1 | 1008 | 1 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | PK_TB | 1 | | 0 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | PK_TA | 1 | | 0 (0)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID | TA | 1 | 1004 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(ROWNUM<=1000)
6 - access("TC"."VERSION">=1 AND "TC"."VERSION"<=10)
8 - access("TC"."VERSION"="TB"."VERSION" AND "TC"."BRANCH#"="TB"."BRANCH#")
filter("TB"."VERSION"<=10 AND "TB"."VERSION">=1)
9 - access("TB"."VERSION"="TA"."VERSION")
filter("TA"."VERSION">=1 AND "TA"."VERSION"<=10)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2418 consistent gets
0 physical reads
0 redo size
1050353 bytes sent via SQL*Net to client
1245 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000 rows processed

>結合がありrownumを使って行数制限している場合には、”大抵の場合" Nested Loop結合にしたほうがいいんですよ! OLTP系の場合は特に. (弱点もありますが...

と書きましたが、”大抵の場合”と書いたのには理由があります。

あえてハッシュ結合にする場合もあるんです。 え!、え!、え〜〜〜〜〜っ!w

”常に遅いけど、Nested Loop結合+rownumにした場合の弱点”は絶対避けたいような場合です。

Nested Loop結合+rownumにした場合の弱点の処理時間が、ハッシュ結合+rownumにした場合の処理時間を大幅に上回り、想定しているバッチ処理時間を超過してしまう可能性が高い場合がそれです。

状況に合わせて使い分けたいですよね。机上だけだと難しいのですが、実マシン、実データで検証を繰り返していれば対処できる問題だと思います。


rownum使って満足しちゃってると..... #1
rownum使って満足しちゃってると..... #2
rownum使って満足しちゃってると..... #3
rownum使って満足しちゃってると..... #4

| | コメント (0) | トラックバック (0)

2013年6月11日 (火)

rownum使って満足しちゃってると..... #4

つづきです。

前回の記事でも、まだ、ぼや〜〜〜〜んとしている方向け! :) #そうじゃない方も


以下は、リアルタムSQLモニタリングの結果ですが、rownumが効いたケース(上の結果)と効かなかったケース(下の結果)の違いは一目瞭然!
rownumの操作を示すCOUNT STOPKEYが上限の1000に満たない状態でId=4〜8のTC表とTB表の結合で100万回ループするNESTED LOOP結合が行われた後、Id=3,9でTA表と100万回ループするNESTED LOOP結合が行われているがよくわかります。


前々回rownumが狙い通りに効いたケースのSQLリアルタイムモニタリング結果

=======================================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Read | Read | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | Reqs | Bytes | (%) | (# samples) |
=======================================================================================================================================================
| 0 | SELECT STATEMENT | | | | 18 | +0 | 1 | 1000 | | | | |
| 1 | COUNT STOPKEY | | | | 18 | +0 | 1 | 1000 | | | | |
| 2 | NESTED LOOPS | | | | 18 | +0 | 1 | 1000 | | | | |
| 3 | NESTED LOOPS | | 1001 | 3798 | 18 | +0 | 1 | 1000 | | | | |
| 4 | NESTED LOOPS | | 2002 | 2796 | 18 | +0 | 1 | 1000 | | | | |
| 5 | TABLE ACCESS BY INDEX ROWID | TC | 4004 | 571 | 18 | +0 | 1 | 1000 | 53 | 1MB | | |
| 6 | INDEX RANGE SCAN | PK_TC | | 13 | 18 | +0 | 1 | 1000 | 3 | 49152 | | |
| 7 | TABLE ACCESS BY INDEX ROWID | TB | 1 | 1 | 18 | +0 | 1000 | 1000 | 2 | 65536 | | |
| 8 | INDEX UNIQUE SCAN | PK_TB | 1 | | 18 | +0 | 1000 | 1000 | 2 | 40960 | | |
| 9 | INDEX UNIQUE SCAN | PK_TA | 1 | | 18 | +0 | 1000 | 1000 | 1 | 8192 | | |
| 10 | TABLE ACCESS BY INDEX ROWID | TA | 1 | 1 | 18 | +0 | 1000 | 1000 | 1 | 8192 | | |
=======================================================================================================================================================

前回rownumが狙い通りに効かなかったケースのSQLリアルタイムモニタリング結果

=========================================================================================================================================
| Id | Operation | Name | Rows | Cost | Time | Start | Execs | Rows | Activity | Activity Detail |
| | | | (Estim) | | Active(s) | Active | | (Actual) | (%) | (# samples) |
=========================================================================================================================================
| 0 | SELECT STATEMENT | | | | | | 1 | | | |
| 1 | COUNT STOPKEY | | | | | | 1 | | | |
| 2 | NESTED LOOPS | | | | | | 1 | | | |
| 3 | NESTED LOOPS | | 1000 | 37910 | 2 | +2 | 1 | 0 | | |
| 4 | NESTED LOOPS | | 20001 | 27906 | 2 | +2 | 1 | 1M | | |
| 5 | TABLE ACCESS BY INDEX ROWID | TC | 40003 | 5675 | 3 | +1 | 1 | 1M | 25.00 | Cpu (1) |
| 6 | INDEX RANGE SCAN | PK_TC | | 109 | 2 | +2 | 1 | 1M | 25.00 | Cpu (1) |
| 7 | TABLE ACCESS BY INDEX ROWID | TB | 1 | 1 | 3 | +2 | 1M | 1M | 25.00 | Cpu (1) |
| 8 | INDEX UNIQUE SCAN | PK_TB | 1 | | 2 | +2 | 1M | 1M | 25.00 | Cpu (1) |
| 9 | INDEX UNIQUE SCAN | PK_TA | 1 | | | | 1M | | | |
| 10 | TABLE ACCESS BY INDEX ROWID | TA | 1 | 1 | | | | | | |
=========================================================================================================================================

rownumって、取得する行数の制限なのであって、NESTED LOOP結合のループ回数を制限するものではないんですよね。

rownumを使っているからもう安心! (キリっ! 

と、言えない状況もあるということはお忘れなく!

現状、この弱点を回避する方法は無いのですから....


rownum使って満足しちゃってると..... #1
rownum使って満足しちゃってると..... #2
rownum使って満足しちゃってると..... #3

| | コメント (1) | トラックバック (0)

2013年6月10日 (月)

rownum使って満足しちゃってると..... #3

つづきです。

rownumの弱点分かりましたよね?

まだ、ぼや〜〜〜〜んとしていますか?


では、以下を見てください。

前々回rownumが狙い通りに効いたケースと、前回rownumが狙い通りに効かなかったケースの駆動表と1つ目の内部表の結合結果です。

どちらのケースでも同じ件数(buffer getsも同じです)がヒットしています。

駆動表TCと1つ目の内部表TBの結合結果は検索範囲は違いますが同じです。

前々回rownumが狙い通りに効いたケースの駆動表TCと1つ目の内部表TBの結合結果

01:45:02 SCOTT> l
1 select count(1)
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 where
8* tc.version between 1 and 10
01:45:03 SCOTT> /

COUNT(1)
----------
1000000

前回rownumが狙い通りに効かなかったケースの駆動表TCと1つ目の内部表TBの結合結果

01:45:52 SCOTT> l
1 select count(1)
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 where
8* tc.version between 11 and 20
01:45:52 SCOTT> /

COUNT(1)
----------
1000000

違いは、2つ目の内部表の結合でヒットするデータが1000件以上あったか、0件だったかの違いだけなんです。


もう少し分かりやすく説明すると、以下の赤字部分まではどちらも同じですが...

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 2952K| 37910 (1)| 00:07:35 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 1000 | 2952K| 37910 (1)| 00:07:35 |
| 4 | NESTED LOOPS | | 20001 | 38M| 27906 (1)| 00:05:35 |
| 5 | TABLE ACCESS BY INDEX ROWID| TC | 40003 | 38M| 5675 (1)| 00:01:09 |
|* 6 | INDEX RANGE SCAN | PK_TC | | | 109 (0)| 00:00:02 |
| 7 | TABLE ACCESS BY INDEX ROWID| TB | 1 | 1008 | 1 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | PK_TB | 1 | | 0 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | PK_TA | 1 | | 0 (0)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID | TA | 1 | 1004 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

以下の赤字部分ID=3のNESTED LOOP部分でヒットするデータが1000件以上あるか、0件かが分かれ道だったんです。

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 2952K| 37910 (1)| 00:07:35 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 1000 | 2952K| 37910 (1)| 00:07:35 |
| 4 | NESTED LOOPS | | 20001 | 38M| 27906 (1)| 00:05:35 |
| 5 | TABLE ACCESS BY INDEX ROWID| TC | 40003 | 38M| 5675 (1)| 00:01:09 |
|* 6 | INDEX RANGE SCAN | PK_TC | | | 109 (0)| 00:00:02 |
| 7 | TABLE ACCESS BY INDEX ROWID| TB | 1 | 1008 | 1 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | PK_TB | 1 | | 0 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | PK_TA | 1 | | 0 (0)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID | TA | 1 | 1004 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------


え、まだ、ぼや〜〜〜〜んとしています? 

んんん〜〜〜〜〜っ

つづく。



rownum使って満足しちゃってると..... #1
rownum使って満足しちゃってると..... #2

| | コメント (0) | トラックバック (0)

2013年6月 8日 (土)

rownum使って満足しちゃってると..... #2

rownumの弱点に気付きました?

では、以下のクエリを見てください。前回の例と検索範囲は多少変えてありますがSQL文やヒントは同じです。実行計画も全く同じです。 

が、

Elapsed timeは、2670msになり、Buffer Getsは、1174942、と、すごいことになっています。

Physical readが発生していないのでこの程度の差ですが、Elapsed timeは、約10倍、Buffer Getsは、なんと、約490倍にもなってます!

しかも、rows processedは、0 です!


なんということでしょう! 0行なのに、Elapsed timeとBuffer Getsが異常に増加してる.....(ニヤニヤ


これがrownumの弱点!

01:43:17 SCOTT> l
1 select /*+ leading(tc) use_nl(tc tb ta) */ *
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 join ta
8 on
9 tb.version = ta.version
10 where
11 tc.version between 11 and 20
12* and rownum <= 1000
01:43:17 SCOTT> /

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

経過: 00:00:02.67

実行計画
----------------------------------------------------------
Plan hash value: 2389411418

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 2952K| 37910 (1)| 00:07:35 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 1000 | 2952K| 37910 (1)| 00:07:35 |
| 4 | NESTED LOOPS | | 20001 | 38M| 27906 (1)| 00:05:35 |
| 5 | TABLE ACCESS BY INDEX ROWID| TC | 40003 | 38M| 5675 (1)| 00:01:09 |
|* 6 | INDEX RANGE SCAN | PK_TC | | | 109 (0)| 00:00:02 |
| 7 | TABLE ACCESS BY INDEX ROWID| TB | 1 | 1008 | 1 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | PK_TB | 1 | | 0 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | PK_TA | 1 | | 0 (0)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID | TA | 1 | 1004 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(ROWNUM<=1000)
6 - access("TC"."VERSION">=11 AND "TC"."VERSION"<=20)
8 - access("TC"."VERSION"="TB"."VERSION" AND "TC"."BRANCH#"="TB"."BRANCH#")
filter("TB"."VERSION">=11 AND "TB"."VERSION"<=20)
9 - access("TB"."VERSION"="TA"."VERSION")
filter("TA"."VERSION">=11 AND "TA"."VERSION"<=20)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
1174942 consistent gets
0 physical reads
0 redo size
905 bytes sent via SQL*Net to client
508 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed

01:43:21 SCOTT>

私が寝る時間欲しさに、データ量をケチったw のでこの程度の差しかありませんが、データ量が多ければもっと酷い結果となっていたことでしょう。


謎は解けましたよね!

 どうしたんだ、Hey , Hey, べいべ〜♪  
 データベースエンジンはビンビンだせ〜♪
 いつものように〜、きめて、1000行までで止ようぜ〜〜♪
.....
 こんな夜に〜♪、 rownumが効かないなんて〜♪
 こんな夜に〜♪、 遅延しちまうなんて〜♪

次回へつづく。



rownum使って満足しちゃってると..... #1

| | コメント (0) | トラックバック (0)

2013年6月 7日 (金)

rownum使って満足しちゃってると..... #1

まずは、以下のクエリを見てください。(適当に作りすぎたので、よい例ではないですが...)

220ms程度で、Buffer Getsが2418程度あります。numrowで制限している最大件数が戻されています。

データが大量にヒットする可能性があるようですね。

rownumで返す行数を制限し、ネステッドループ結合にしてチューニングするケースは多々あります。


00:44:53 SCOTT> set autot trace exp stat
01:42:37 SCOTT> l
1 select /*+ leading(tc) use_nl(tc tb ta) */ *
2 from
3 tc join tb
4 on
5 tc.version = tb.version
6 and tc.branch# = tb.branch#
7 join ta
8 on
9 tb.version = ta.version
10 where
11 tc.version between 1 and 10
12* and rownum <= 1000
01:42:37 SCOTT> /

1000行が選択されました。

経過: 00:00:00.22

実行計画
----------------------------------------------------------
Plan hash value: 2389411418

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 2952K| 3798 (1)| 00:00:46 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 1001 | 2955K| 3798 (1)| 00:00:46 |
| 4 | NESTED LOOPS | | 2002 | 3947K| 2796 (1)| 00:00:34 |
| 5 | TABLE ACCESS BY INDEX ROWID| TC | 4004 | 3953K| 571 (0)| 00:00:07 |
|* 6 | INDEX RANGE SCAN | PK_TC | | | 13 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID| TB | 1 | 1008 | 1 (0)| 00:00:01 |
|* 8 | INDEX UNIQUE SCAN | PK_TB | 1 | | 0 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | PK_TA | 1 | | 0 (0)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID | TA | 1 | 1004 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter(ROWNUM<=1000)
6 - access("TC"."VERSION">=1 AND "TC"."VERSION"<=10)
8 - access("TC"."VERSION"="TB"."VERSION" AND "TC"."BRANCH#"="TB"."BRANCH#")
filter("TB"."VERSION"<=10 AND "TB"."VERSION">=1)
9 - access("TB"."VERSION"="TA"."VERSION")
filter("TA"."VERSION">=1 AND "TA"."VERSION"<=10)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2418 consistent gets
0 physical reads
0 redo size
1050353 bytes sent via SQL*Net to client
1245 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000 rows processed

01:42:39 SCOTT>

しかし、rownumも万能ではないんですよね。弱点があるんです。

どのような弱点なのか、気付きましたか?

ヒントは、 ID=1のOperationであるCOUNT STOPKEYとそれ以降にあるNESTED LOOPSの動き。


ということで、次回へつづく。

| | コメント (0) | トラックバック (0)

2013年4月28日 (日)

db file scattered read と db file parallel read と db file sequential read (最終回)

無理矢理引っ張った感じですが、db file scattered read と db file parallel read と db file sequential readの最終回。

今日は、db file parallel read に注目してみようと思います。


db file parallel readはどのような待機イベントだったか再確認しておきましょう!

マニュアルでは以下のように説明されています。

db file parallel read
リカバリ時のイベントです。
バッファ・プリフェッチ中に、最適化(複数のシングル・ブロック読取りの実行ではない)として発生する可能性もあります。
リカバリ時に変更が必要となったデータベース・ブロックはデータベースからパラレルに読み込まれます。

すべてのI/Oが完了するまでの時間が待機時間となる

少々わかりずらいですが、「バッファ・プリフェッチ中に、最適化(複数のシングル・ブロック読取りの実行ではない)として発生する可能性もあります。」ってところがポイントですよね。(リカバリしているわけではないので!)

でてきましたね、 prefetch という単語が!

斜め読みしちゃうと?となりそうですが、「複数のシングル・ブロック読取りの実行ではない」という箇所からもシングル・ブロック読み取りの繰り返しではなく、一括読み込み的なI/O最適化に関連した動き、だろうな〜ということは想像できます.
db file scattered read と db file parallel read と db file sequential read (その6)でも簡単に記載しているので参考に)

連続したブロックを一括読取りするのは、 db file scattered read
単一ブロックをブロック単位で読み取るのは、 db file sequential read
そして、不連続な複数ブロックを一括読取りするのは、db file parallel read


不連続ってところもポイントですね! 連続してないんですよ!

そこで、db file scattered read と db file parallel read と db file sequential read (その1)に書いた赤字部分が鍵になってきます!

TABLE_NAME                     INDEX_NAME                       NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
------------------------------ ------------------------------ ---------- ------------- -----------------
HIGH_CLUSTERING_FACTOR PK_HIGH_CLUSTERING_FACTOR 100000 100000 99978
LOW_CLUSTERING_FACTOR PK_LOW_CLUSTERING_FACTOR 100000 100000 4348

CLUSTERING_FACTORってNUM_ROWSに近ければ近いほど、索引のキー順に行を読んでしまうと読んだ行数ど同じ程度のデータブロックを読み込む必要があるということを示しています。
(マニュアルにも書いてますよね。Oracle® Databaseパフォーマンス・チューニング・ガイド11gリリース2 (11.2) - 11.2.3.1 ブロックのI/O(行ではなく)の想定

マニュアル、読みました?? 

ほんとに? 
(マニュアル読むの大切ですよ〜マニュアル読んで、試して、大きくなった、私が通りますよ〜〜〜と。)

では、細かい説明はもういいですよね! (^^;;;

で、
INDEX RANGE SCANとなっている場合、CLUSTERING_FACTORが低い場合と高い場合では、読み込みブロック数に差がある、低い場合は、少ないブロック読み込みで済むが、高い場合はより多くのブロックを読み込む必要があるということになります :)


効率的なI/Oを目指すOracleさんなので....CLUSTERING_FACTORが高く..

select
/*+
leading(t2 t1)
use_nl(t2 t1)
index(t2 pk_high_clustering_factor)
*/
t2.id
,t2.name
,t1.name
from
low_clustering_factor t1
inner join high_clustering_factor t2
on
t1.id = t2.id
where
t2.id between 30001 and 35000
/

こんな実行計画になっていて、かつ、physical reads prefetch warmupが発生していない状況で、バッファキャッシュヒット率が悪いと物理読み込みが発生して....

Rows     Row Source Operation
------- ---------------------------------------------------
2421 NESTED LOOPS (cr=5252 pr=2841 pw=0 time=2771706 us)
2421 NESTED LOOPS (cr=2831 pr=2522 pw=0 time=4204941 us cost=5012 size=1530612 card=2501)
2421 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2589 pr=2458 pw=0 time=4188001 us cost=2510 size=765612 card=2502)
2421 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=168 pr=121 pw=0 time=7663 us cost=8 size=0 card=2502)(object id 82774)
2421 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=242 pr=64 pw=0 time=0 us cost=0 size=0 card=1)(object id 82772)
2421 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2421 pr=319 pw=0 time=0 us cost=1 size=306 card=1)

db file sequential read以外に、db file parallel readが発生するんですよね。

Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 163 0.00 0.00
SQL*Net message from client 163 0.96 3.36
db file parallel read 161 0.01 0.63
SQL*Net more data to client 161 0.00 0.00
db file sequential read 14 0.00 0.00
********************************************************************************

INDEX RANGE SCANとなっている場合、CLUSTERING_FACTORが低い場合と高い場合では、読み込みブロック数に差がある、低い場合は、少ないブロック読み込みで済むが、高い場合はより多くのブロックを読み込む必要があるということになりますよね :)

と書きましたが、それを確認する簡単な実験を一つ。

バッファキャッシュヒット率が100%の状態でauto traceした結果です。
同一件数ヒットするクエリかつ、実行計画もINDEX RANGE SCANなのですが、
CLUSTERING_FACTORが低い表は、consistent getsが少なく、CLUSTERING_FACTORが高い表は、consistent getsが多くなるという差が発生しているのに気づきましたか?
(どちらの表も同じ定義の表なのですが、CLUSTERING_FACTORだけは変えてあります。)


注)CLUSTERING_FACTORが高い場合オプティマイザは索引を利用したアクセス効率が悪いと判断しTABLE FULL SCANを行う場合もあります。(以下の例ではヒントでINDEX RANGE SCANを強制しています)

select * from low_clustering_factor where id between 1000 and 3057;

2058行が選択されました。

経過: 00:00:00.09

実行計画
----------------------------------------------------------
Plan hash value: 2077556633

--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2059 | 615K| 95 (0)| 00:00:02 |
| 1 | TABLE ACCESS BY INDEX ROWID| LOW_CLUSTERING_FACTOR | 2059 | 615K| 95 (0)| 00:00:02 |
|* 2 | INDEX RANGE SCAN | PK_LOW_CLUSTERING_FACTOR | 2059 | | 5 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID">=1000 AND "ID"<=3057)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
365 consistent gets
0 physical reads
0 redo size
666586 bytes sent via SQL*Net to client
1923 bytes received via SQL*Net from client
139 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2058 rows processed

select /*+ INDEX_ASC(t1 pk_high_clustering_factor) */ * from high_clustering_factor t1 where id between 1000 and 5000;

2058行が選択されました。

経過: 00:00:00.10

実行計画
----------------------------------------------------------
Plan hash value: 3928373190

---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2002 | 598K| 2009 (0)| 00:00:25 |
| 1 | TABLE ACCESS BY INDEX ROWID| HIGH_CLUSTERING_FACTOR | 2002 | 598K| 2009 (0)| 00:00:25 |
|* 2 | INDEX RANGE SCAN | PK_HIGH_CLUSTERING_FACTOR | 2002 | | 7 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("ID">=1000 AND "ID"<=5000)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2201 consistent gets
0 physical reads
0 redo size
666587 bytes sent via SQL*Net to client
1923 bytes received via SQL*Net from client
139 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2058 rows processed


各SQL文実行時に物理I/Oが発生しCLUSTERING_FACTORが高ければ、db file parallel readが発生することに...

※SQLトレース(tkprofで整形済み)

SQL ID: ap97zhtgsmt34
Plan Hash: 2077556633
select *
from
low_clustering_factor where id between 1000 and 3057


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 139 0.03 0.07 96 365 0 2058
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 141 0.03 0.07 96 365 0 2058

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84

Rows Row Source Operation
------- ---------------------------------------------------
2058 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=365 pr=96 pw=0 time=94236 us cost=95 size=630054 card=2059)
2058 INDEX RANGE SCAN PK_LOW_CLUSTERING_FACTOR (cr=143 pr=6 pw=0 time=3630 us cost=5 size=0 card=2059)(object id 82772)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 139 0.00 0.00
db file sequential read 96 0.00 0.05
SQL*Net message from client 139 0.02 1.09
********************************************************************************

SQL ID: fmynvkvbvrv49
Plan Hash: 3928373190
select /*+ INDEX_ASC(t1 pk_high_clustering_factor) */ *
from
high_clustering_factor t1 where id between 1000 and 5000


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 139 0.28 1.85 1647 2201 0 2058
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 141 0.28 1.85 1647 2201 0 2058

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84

Rows Row Source Operation
------- ---------------------------------------------------
2058 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2201 pr=1647 pw=0 time=1756678 us cost=2009 size=612612 card=2002)
2058 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=144 pr=6 pw=0 time=8099 us cost=7 size=0 card=2002)(object id 82774)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 139 0.00 0.00
db file sequential read 1594 0.01 1.66
SQL*Net message from client 139 0.02 1.68
db file parallel read 6 0.00 0.00
********************************************************************************


あ〜〜〜すっきり! :)

みなさん、よいゴールデンウィークを!




バックナンバー

db file scattered read と db file parallel read と db file sequential read (その1)
db file scattered read と db file parallel read と db file sequential read (その2)
db file scattered read と db file parallel read と db file sequential read (その3)
db file scattered read と db file parallel read と db file sequential read (その4)
db file scattered read と db file parallel read と db file sequential read (その5)
db file scattered read と db file parallel read と db file sequential read (その6)
db file scattered read と db file parallel read と db file sequential read (その7)
db file scattered read と db file parallel read と db file sequential read (その8)

| | コメント (0) | トラックバック (0)

2013年4月26日 (金)

db file scattered read と db file parallel read と db file sequential read (その8)

>次回はphysical reads prefetch warmupを発生させます。(引っ張り過ぎw

の予告通り、physical reads prefetch warmupを発生させてみます!

やったことをほぼそのまま書いておきますね:)

オラクルさん、ディクショナリーやらなんやらいろいろ読んでしまって、小さめのバッファキャッシュだとそれらでお腹いっぱいになったりするんだよねw (よく考えられてる:)


23:29:24 SYS> startup
ORACLEインスタンスが起動しました。

Total System Global Area 313880576 bytes
Fixed Size 1336176 bytes
Variable Size 125832336 bytes
Database Buffers 180355072 bytes
Redo Buffers 6356992 bytes
データベースがマウントされました。
データベースがオープンされました。
23:29:47 SYS> select sid,serial# from v$session where username='SCOTT';

SID SERIAL#
---------- ----------
63 11


23:30:06 SYS> select name,value from v$sesstat vss join v$statname vsn on vss.statistic# = vsn.statistic#
where (name like '%prefetch%' or name like '%physical read%') and sid=63 order by name
/
NAME VALUE
---------------------------------------------------------------- ----------
index crx upgrade (prefetch) 0
physical read IO requests 89
physical read bytes 729088
physical read flash cache hits 0
physical read requests optimized 0
physical read total IO requests 89
physical read total bytes 729088
physical read total multi block requests 0
physical reads 89
physical reads cache 89
physical reads cache prefetch 0
physical reads direct 0
physical reads direct (lob) 0
physical reads direct temporary tablespace 0
physical reads for flashback new 0
physical reads prefetch warmup 0
physical reads retry corrupt 0
prefetch clients - 16k 0
prefetch clients - 2k 0
prefetch clients - 32k 0
prefetch clients - 4k 0
prefetch clients - 8k 0
prefetch clients - default 0
prefetch clients - keep 0
prefetch clients - recycle 0
prefetch warmup blocks aged out before use 0
prefetch warmup blocks flushed out before use 0
prefetched blocks aged out before use 0


※指定セッションのセッションレベルのSQLトレース開始!


23:30:27 SYS> exec dbms_monitor.session_trace_enable(63,11,true,false,null);

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


....この間に、db file * read祭りになるクエリをSCOTTユーザで実行します!....


※指定セッションのセッションレベルSQLトレース終了!


23:32:21 SYS> exec dbms_monitor.session_trace_disable(63,11);

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


:) db file scattered readが発生しています! 対象セッションのセッション統計をみて確認みると....

※SQLトレース結果(tkprofで整形済み)


select
/*+
leading(t2 t1)
use_nl(t2 t1)
index(t2 pk_high_clustering_factor)
*/
t2.id
,t2.name
,t1.name
from
low_clustering_factor t1
inner join high_clustering_factor t2
on
t1.id = t2.id
where
t2.id between 30001 and 35000

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.01 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 163 0.27 1.81 2364 5252 0 2421
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 165 0.28 1.82 2364 5252 0 2421

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84

Rows Row Source Operation
------- ---------------------------------------------------
2421 NESTED LOOPS (cr=5252 pr=2364 pw=0 time=7618698 us)
2421 NESTED LOOPS (cr=2831 pr=2104 pw=0 time=8830835 us cost=5012 size=1530612 card=2501)
2421 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2589 pr=2070 pw=0 time=8807144 us cost=2510 size=765612 card=2502)
2421 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=168 pr=34 pw=0 time=3764 us cost=8 size=0 card=2502)(object id 82774)
2421 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=242 pr=34 pw=0 time=0 us cost=0 size=0 card=1)(object id 82772)
2421 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2421 pr=260 pw=0 time=0 us cost=1 size=306 card=1)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 163 0.00 0.00
Disk file operations I/O 1 0.00 0.00
db file sequential read 251 0.03 0.40
db file scattered read 37 0.02 0.23
SQL*Net message from client 163 0.02 1.71
SQL*Net more data to client 161 0.00 0.00
db file parallel read 160 0.08 1.07
********************************************************************************

select
/*+
leading(t1 t2)
use_nl(t1 t2)
index(t1 pk_low_clustering_factor)
*/
t1.id
,t1.name
,t2.name
from
low_clustering_factor t1
inner join high_clustering_factor t2
on
t1.id = t2.id
where
t1.id between 1 and 5000

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 173 0.24 0.77 1302 3367 0 2566
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 175 0.24 0.78 1302 3367 0 2566

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84

Rows Row Source Operation
------- ---------------------------------------------------
2566 NESTED LOOPS (cr=3367 pr=1302 pw=0 time=913653 us)
2566 NESTED LOOPS (cr=801 pr=232 pw=0 time=324655 us cost=5231 size=1530000 card=2500)
5000 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=564 pr=226 pw=0 time=78984 us cost=229 size=1530000 card=5000)
5000 INDEX RANGE SCAN PK_LOW_CLUSTERING_FACTOR (cr=181 pr=8 pw=0 time=5896 us cost=11 size=0 card=5000)(object id 82772)
2566 INDEX UNIQUE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=237 pr=6 pw=0 time=0 us cost=0 size=0 card=1)(object id 82774)
2566 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2566 pr=1070 pw=0 time=0 us cost=1 size=306 card=1)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 173 0.00 0.00
db file sequential read 1302 0.00 0.62
SQL*Net message from client 172 0.01 1.66
SQL*Net more data to client 171 0.00 0.00

よかったよかった、うまく physical reads prefetch warmupが増加しててw (^^


NAME                                                                  VALUE
---------------------------------------------------------------- ----------
index crx upgrade (prefetch) 0
physical read IO requests 3294
physical read bytes 30957568
physical read flash cache hits 0
physical read requests optimized 0
physical read total IO requests 3294
physical read total bytes 30957568
physical read total multi block requests 15
physical reads 3779
physical reads cache 3779
physical reads cache prefetch 1916
physical reads direct 0
physical reads direct (lob) 0
physical reads direct temporary tablespace 0
physical reads for flashback new 0
physical reads prefetch warmup 485
physical reads retry corrupt 0
prefetch clients - 16k 0
prefetch clients - 2k 0
prefetch clients - 32k 0
prefetch clients - 4k 0
prefetch clients - 8k 0
prefetch clients - default 0
prefetch clients - keep 0
prefetch clients - recycle 0
prefetch warmup blocks aged out before use 20
prefetch warmup blocks flushed out before use 0
prefetched blocks aged out before use 0


physical reads prefetch warmupってその名の通り、warmupなんですよね。バッファキャッシュの。

いつもより余計に読み込んでるからって驚かないでね!

おまけ。

起動直後には、SYS関連のオブジェクトがバッファキャッシュに読み込まれてしまって、physical read prefetch warmupが発生しないこともなんどかありました。

で、試しに、バッファキャッシュをクリアしてみたんですね...そしたら、warmupするじゃないですか!


23:15:00 SYS> startup
ORACLEインスタンスが起動しました。

Total System Global Area 313880576 bytes
Fixed Size 1336176 bytes
Variable Size 125832336 bytes
Database Buffers 180355072 bytes
Redo Buffers 6356992 bytes
データベースがマウントされました。
データベースがオープンされました。
23:15:18 SYS> select status,count(1) from v$bh group by status;

STATUS COUNT(1)
---------- ----------
xcur 5413
cr 165


※このとき既にバッファキャッシュはイッパイに! なにが乗っていたかというと..

SYS関連のオブジェクト...


OBJECT_NAME                    STATUS       COUNT(1)
------------------------------ ---------- ----------
ACCESS$ xcur 49
AQ$_MGMT_TASK_QTABLE_I xcur 2
AQ$_QTABLE_AFFINITIES_PK xcur 1
AQ$_QUEUES xcur 1
AQ$_QUEUES_CHECK xcur 1
AQ$_QUEUE_TABLES xcur 1
AQ$_QUEUE_TABLES_PRIMARY xcur 1
AQ$_QUEUE_TABLE_AFFINITIES xcur 2
AQ$_SCHEDULES xcur 1
AQ$_SUBSCRIBER_TABLE xcur 2
AQ$_SUBSCRIBER_TABLE_PRIMARY xcur 1
ARGUMENT$ xcur 11
ASSOC1 xcur 1
C_COBJ# xcur 45
C_FILE#_BLOCK# cr 4
C_FILE#_BLOCK# xcur 141
C_OBJ# xcur 159
C_OBJ#_INTCOL# xcur 319
C_TOID_VERSION# xcur 2821
C_TS# xcur 13
C_USER# xcur 6
DAM_CONFIG_PARAM$ xcur 2
DBMS_LOCK_ALLOCATED xcur 1
DEPENDENCY$ xcur 60
EDITION$ xcur 2
EMDW_TRACE_CONFIG_IDX_01 xcur 1

.... 以下略 ...


バッファキャッシュ、フラ〜〜〜ッシュ! ❤


23:15:35 SYS> alter system flush buffer_cache;

システムが変更されました。


※バッファキャッシュにはfreeができました。


23:15:46 SYS> select status,count(1) from v$bh group by status;

STATUS COUNT(1)
---------- ----------
xcur 2
free 5576


※クエリ実行前のセッション統計


NAME                                                                  VALUE
---------------------------------------------------------------- ----------
index crx upgrade (prefetch) 0
physical read IO requests 88
physical read bytes 720896
physical read flash cache hits 0
physical read requests optimized 0
physical read total IO requests 88
physical read total bytes 720896
physical read total multi block requests 0
physical reads 88
physical reads cache 88
physical reads cache prefetch 0
physical reads direct 0
physical reads direct (lob) 0
physical reads direct temporary tablespace 0
physical reads for flashback new 0
physical reads prefetch warmup 0
physical reads retry corrupt 0
prefetch clients - 16k 0
prefetch clients - 2k 0
prefetch clients - 32k 0
prefetch clients - 4k 0
prefetch clients - 8k 0
prefetch clients - default 0
prefetch clients - keep 0
prefetch clients - recycle 0
prefetch warmup blocks aged out before use 0
prefetch warmup blocks flushed out before use 0
prefetched blocks aged out before use 0


いつものクエリを実行.....

...中略...


※実行後、該当セッションのセッション統計を見てみると...


NAME                                                                  VALUE
---------------------------------------------------------------- ----------
index crx upgrade (prefetch) 0
physical read IO requests 3026
physical read bytes 32858112
physical read flash cache hits 0
physical read requests optimized 0
physical read total IO requests 3026
physical read total bytes 32858112
physical read total multi block requests 27
physical reads 4011
physical reads cache 4011
physical reads cache prefetch 2376
physical reads direct 0
physical reads direct (lob) 0
physical reads direct temporary tablespace 0
physical reads for flashback new 0
physical reads prefetch warmup 985
physical reads retry corrupt 0
prefetch clients - 16k 0
prefetch clients - 2k 0
prefetch clients - 32k 0
prefetch clients - 4k 0
prefetch clients - 8k 0
prefetch clients - default 0
prefetch clients - keep 0
prefetch clients - recycle 0
prefetch warmup blocks aged out before use 65
prefetch warmup blocks flushed out before use 0
prefetched blocks aged out before use 0

バッファキャッシュクリアしても出ることあるんだね。


これでおしまい。  あ、一つ思い出したので、次回にでも。

つづく。




バックナンバー

db file scattered read と db file parallel read と db file sequential read (その1)
db file scattered read と db file parallel read と db file sequential read (その2)
db file scattered read と db file parallel read と db file sequential read (その3)
db file scattered read と db file parallel read と db file sequential read (その4)
db file scattered read と db file parallel read と db file sequential read (その5)
db file scattered read と db file parallel read と db file sequential read (その6)
db file scattered read と db file parallel read と db file sequential read (その7)

| | コメント (0) | トラックバック (0)

2013年4月24日 (水)

db file scattered read と db file parallel read と db file sequential read (その7)

前回、物理読み込みが発生しない状況を作りdb file * read関連の物理読み込みが発生しない状況ではphysical reads prefetch warmup/cache prefetchが発生していないことを確認しました。

ということで、続きです。


physical reads prefetch warmupってどのような状況で増加するのか、待機イベントはどのようなものなのか確認してみることに..... (もうわかってるくせに〜w)

physical reads prefetch warmupは以下のように説明されています。
- バッファ・キャッシュの自動プリウォーム中にディスクから読み取られたデータ・ブロックの数 / class - 8 : cache

また、津島博士のパフォーマンス講座 第13回 キャッシュ周りについて でも説明されています。(この手の解説をマニュアルで読んだ記憶がないが、どこかに記載されてるのかな?....)

では、どのような待機イベントが発生するのか...
試してガッテン、じゃなくて、試して納得!でおなじみの、しばちょう先生のセッションの資料(ODDD2011)によると、db file scattered readが発生するようですね。


なるほど....どの程度かわかりませんが、バッファキャッシュにある程度データブロックが乗っかっているとphysical reads prefetch warmupって発生しないんだろうなぁ。というところまでは、なんとなく理解できました。:)


さっそく、physical reads prefetch warmupを発生させてみましょう!...

と、その前に、physical reads prefetch warmupは発生させず、physical reads cache prefetchだけを発生させてみますw

バッファキャッシュは本検証で実行するクエリでアクセスする表や索引以外のブロックで満たしてあります。

v$bhの状態


SYS> select status,count(1) from v$bh group by status;

STATUS COUNT(1)
---------- ----------
xcur 8903
cr 20

という状況(物理読み込みが発生しない状況)でdb file * read祭りになるクエリを実行してみることにします。

クエリを実行する前のセッション統計(prefetch関連のみ)は以下の通り。


22:04:51 SYS> l
1 select
2 name
3 ,value
4 from
5 v$sesstat vss
6 join v$statname vsn
7 on
8 vss.statistic# = vsn.statistic#
9 where
10 (
11 name like '%prefetch%'
12 or name like '%physical read%'
13 )
14 and sid=195
15 order by
16* name
22:04:52 SYS> /


NAME VALUE
---------------------------------------------------------------- ----------
index crx upgrade (prefetch) 0
physical read IO requests 479
physical read bytes 7012352
physical read flash cache hits 0
physical read requests optimized 0
physical read total IO requests 479
physical read total bytes 7012352
physical read total multi block requests 2
physical reads 856
physical reads cache 856
physical reads cache prefetch 377
physical reads direct 0
physical reads direct (lob) 0
physical reads direct temporary tablespace 0
physical reads for flashback new 0
physical reads prefetch warmup 0
physical reads retry corrupt 0
prefetch clients - 16k 0
prefetch clients - 2k 0
prefetch clients - 32k 0
prefetch clients - 4k 0
prefetch clients - 8k 0
prefetch clients - default 0
prefetch clients - keep 0
prefetch clients - recycle 0
prefetch warmup blocks aged out before use 10
prefetch warmup blocks flushed out before use 0
prefetched blocks aged out before use 0


※SQLトレース(tkprofにて整形済み)


select
/*+
leading(t2 t1)
use_nl(t2 t1)
index(t2 pk_high_clustering_factor)
*/
t2.id
,t2.name
,t1.name
from
low_clustering_factor t1
inner join high_clustering_factor t2
on
t1.id = t2.id
where
t2.id between 30001 and 35000

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 163 0.37 1.46 2084 5252 0 2421
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 165 0.38 1.47 2084 5252 0 2421

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84

Rows Row Source Operation
------- ---------------------------------------------------
2421 NESTED LOOPS (cr=5252 pr=2084 pw=0 time=898088 us)
2421 NESTED LOOPS (cr=2831 pr=1866 pw=0 time=2127562 us cost=5012 size=1530612 card=2501)
2421 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2589 pr=1855 pw=0 time=2109985 us cost=2510 size=765612 card=2502)
2421 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=168 pr=8 pw=0 time=14251 us cost=8 size=0 card=2502)(object id 82774)
2421 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=242 pr=11 pw=0 time=0 us cost=0 size=0 card=1)(object id 82772)
2421 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2421 pr=218 pw=0 time=0 us cost=1 size=306 card=1)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 163 0.00 0.00
db file sequential read 624 0.01 0.84
SQL*Net message from client 163 0.02 2.22
SQL*Net more data to client 161 0.00 0.00
db file parallel read 141 0.02 0.42
********************************************************************************

select
/*+
leading(t1 t2)
use_nl(t1 t2)
index(t1 pk_low_clustering_factor)
*/
t1.id
,t1.name
,t2.name
from
low_clustering_factor t1
inner join high_clustering_factor t2
on
t1.id = t2.id
where
t1.id between 1 and 5000

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 173 0.31 0.85 1383 3367 0 2566
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 175 0.31 0.85 1383 3367 0 2566

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84

Rows Row Source Operation
------- ---------------------------------------------------
2566 NESTED LOOPS (cr=3367 pr=1383 pw=0 time=1180156 us)
2566 NESTED LOOPS (cr=801 pr=233 pw=0 time=340045 us cost=5231 size=1530000 card=2500)
5000 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=564 pr=227 pw=0 time=98730 us cost=229 size=1530000 card=5000)
5000 INDEX RANGE SCAN PK_LOW_CLUSTERING_FACTOR (cr=181 pr=9 pw=0 time=7998 us cost=11 size=0 card=5000)(object id 82772)
2566 INDEX UNIQUE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=237 pr=6 pw=0 time=0 us cost=0 size=0 card=1)(object id 82774)
2566 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2566 pr=1150 pw=0 time=0 us cost=1 size=306 card=1)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 173 0.00 0.00
db file sequential read 1383 0.01 0.66
SQL*Net message from client 172 0.02 2.24
SQL*Net more data to client 171 0.00 0.00

実行後のセッション統計を見てみると....おおおおおおおお!

physical reads cache prefetchは増加しているのでprefetchは発生していますが、physical reads prefetch warmupは発生していません。


NAME                                                                  VALUE
---------------------------------------------------------------- ----------
index crx upgrade (prefetch) 0
physical read IO requests 3963
physical read bytes 35553280
physical read flash cache hits 0
physical read requests optimized 0
physical read total IO requests 3963
physical read total bytes 35553280
physical read total multi block requests 2
physical reads 4340
physical reads cache 4340
physical reads cache prefetch 1696
physical reads direct 0
physical reads direct (lob) 0
physical reads direct temporary tablespace 0
physical reads for flashback new 0
physical reads prefetch warmup 0
physical reads retry corrupt 0
prefetch clients - 16k 0
prefetch clients - 2k 0
prefetch clients - 32k 0
prefetch clients - 4k 0
prefetch clients - 8k 0
prefetch clients - default 0
prefetch clients - keep 0
prefetch clients - recycle 0
prefetch warmup blocks aged out before use 10
prefetch warmup blocks flushed out before use 0
prefetched blocks aged out before use 0

physical reads prefetch warmupとphysical reads cache prefetchが同時に増加していた時に比べ、発生している待機イベントに違いがあることに気づきましたか? :) そう、db file scattered readが発生していないんですよ!

次回はphysical reads prefetch warmupを発生させます。(引っ張り過ぎw


ところで、prefetch warmup blocks aged out before useという統計値が増加していますよね。
キャッシュバッファを暖めただけで利用されずに捨てられたデータブロック数をカウントしているようです。(マニュアルに記載されていないので、想像ですが)

悲しいですね、使われずに捨てられるなんて。


バックナンバー

db file scattered read と db file parallel read と db file sequential read (その1)
db file scattered read と db file parallel read と db file sequential read (その2)
db file scattered read と db file parallel read と db file sequential read (その3)
db file scattered read と db file parallel read と db file sequential read (その4)
db file scattered read と db file parallel read と db file sequential read (その5)
db file scattered read と db file parallel read と db file sequential read (その6)

| | コメント (0) | トラックバック (0)

2013年4月14日 (日)

db file scattered read と db file parallel read と db file sequential read (その6)

続きです。

system callからも読み取れますが、各待機イベントでは
db file sequential read - 単一ブロック読み込み
db file parallel read - 不連続ブロックの複数ブロックの読み込み
db file scattered read - 連続したブロックの複数ブロック読み込み

が行われ、読み込まれたブロックはバッファキャッシュに乗せられるわけです。

注)
今回は発生しないようにしていますが、Oracle11g R2では実行計画上フルスキャンとなっていても、db file scattered readまたはdirect path read(バッファキャッシュを経由しない)になる場合があります。(smart flash cacheが無い場合)

db file scattered read vs direct path read関連の話題は以下のブログを参考に :)
10046 trace name context forever - Oracle OpenWorld 2012 Unconference presented by JPOUG
10046 trace name context forever - Smart Flash Cache 簡単なパフォーマンス比較
技術情報| Insight Techology, Inc. Part 7 - 11gR2からのフルスキャン
Ask Tom "Direct path reads 11gR2" - Thanks for the question regarding "Direct path reads 11gR2", version 11.2.0.1

direct path readが発生していないので :)
...キャッシュヒット率100%ならば発生しないってこと、ですよね。

試してみます!

バッファキャッシュには以下のSQL文でアクセスする表以外のオブジェクトも含め事前に乗せてあります。
以下SQL文では表、索引ともバッファキャッシュヒット率100%になるようにしてあります。

v$bhの状態(STATUS=freeがないので空きブロックの無い状態)

STATUS       COUNT(1)
---------- ----------
xcur 8904
cr 21

SQLトレースを見ると物理読み込みは発生していないこと、db file * read待機イベントが発生していないことが確認できます!

SQLトレース(tkprofにて整形後)

select
/*+
leading(t2 t1)
use_nl(t2 t1)
index(t2 pk_high_clustering_factor)
*/
t2.id
,t2.name
,t1.name
from
low_clustering_factor t1
inner join high_clustering_factor t2
on
t1.id = t2.id
where
t2.id between 30001 and 35000

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 163 0.06 0.07 0 5252 0 2421
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 165 0.06 0.07 0 5252 0 2421

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 84

Rows Row Source Operation
------- ---------------------------------------------------
2421 NESTED LOOPS (cr=5252 pr=0 pw=0 time=63861 us)
2421 NESTED LOOPS (cr=2831 pr=0 pw=0 time=67887 us cost=5012 size=1530612 card=2501)
2421 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2589 pr=0 pw=0 time=51456 us cost=2510 size=765612 card=2502)
2421 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=168 pr=0 pw=0 time=7260 us cost=8 size=0 card=2502)(object id 82774)
2421 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=242 pr=0 pw=0 time=0 us cost=0 size=0 card=1)(object id 82772)
2421 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2421 pr=0 pw=0 time=0 us cost=1 size=306 card=1)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 163 0.00 0.00
SQL*Net message from client 163 0.02 2.51
SQL*Net more data to client 161 0.00 0.00
********************************************************************************

select
/*+
leading(t1 t2)
use_nl(t1 t2)
index(t1 pk_low_clustering_factor)
*/
t1.id
,t1.name
,t2.name
from
low_clustering_factor t1
inner join high_clustering_factor t2
on
t1.id = t2.id
where
t1.id between 1 and 5000

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 173 0.04 0.05 0 3367 0 2566
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 175 0.04 0.05 0 3367 0 2566

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 84

Rows Row Source Operation
------- ---------------------------------------------------
2566 NESTED LOOPS (cr=3367 pr=0 pw=0 time=44887 us)
2566 NESTED LOOPS (cr=801 pr=0 pw=0 time=32245 us cost=5231 size=1530000 card=2500)
5000 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=564 pr=0 pw=0 time=17496 us cost=229 size=1530000 card=5000)
5000 INDEX RANGE SCAN PK_LOW_CLUSTERING_FACTOR (cr=181 pr=0 pw=0 time=3624 us cost=11 size=0 card=5000)(object id 82772)
2566 INDEX UNIQUE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=237 pr=0 pw=0 time=0 us cost=0 size=0 card=1)(object id 82774)
2566 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2566 pr=0 pw=0 time=0 us cost=1 size=306 card=1)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 173 0.00 0.00
SQL*Net message from client 172 0.07 2.01
SQL*Net more data to client 171 0.00 0.00

db file * read祭りだった際には明らかに増加していた physical reads prefetch warmupやphysical reads cache prefetchも含め、
物理読み込みが発生しない状況であれば、それらの待機イベントも発生しないのは当然と言えば当然ですよね。 :)

SQL実行前のシステム統計(physical readとprefetch関連のみ)


---------------------------------------------------------------- ----------
index crx upgrade (prefetch) 0
physical read IO requests 2201
physical read bytes 18030592
physical read flash cache hits 0
physical read requests optimized 0
physical read total IO requests 2201
physical read total bytes 18030592
physical read total multi block requests 0
physical reads 2201
physical reads cache 2201
physical reads cache prefetch 741
physical reads direct 0
physical reads direct (lob) 0
physical reads direct temporary tablespace 0
physical reads for flashback new 0
physical reads prefetch warmup 0
physical reads retry corrupt 0
prefetch clients - 16k 0
prefetch clients - 2k 0
prefetch clients - 32k 0
prefetch clients - 4k 0
prefetch clients - 8k 0
prefetch clients - default 0
prefetch clients - keep 0
prefetch clients - recycle 0
prefetch warmup blocks aged out before use 0
prefetch warmup blocks flushed out before use 0
prefetched blocks aged out before use 0

SQL文実行後のシステム統計(physical readとprefetch関連のみ)

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
index crx upgrade (prefetch) 0
physical read IO requests 2201
physical read bytes 18030592
physical read flash cache hits 0
physical read requests optimized 0
physical read total IO requests 2201
physical read total bytes 18030592
physical read total multi block requests 0
physical reads 2201
physical reads cache 2201
physical reads cache prefetch 741
physical reads direct 0
physical reads direct (lob) 0
physical reads direct temporary tablespace 0
physical reads for flashback new 0
physical reads prefetch warmup 0
physical reads retry corrupt 0
prefetch clients - 16k 0
prefetch clients - 2k 0
prefetch clients - 32k 0
prefetch clients - 4k 0
prefetch clients - 8k 0
prefetch clients - default 0
prefetch clients - keep 0
prefetch clients - recycle 0
prefetch warmup blocks aged out before use 0
prefetch warmup blocks flushed out before use 0
prefetched blocks aged out before use 0

物理読み込みが発生しない状況を作りdb file * read関連の物理読み込みが発生しない状況で、physical reads prefetch warmup/cache prefetchが発生していないことは確認しました。

次回はphysical reads prefetch warmupってどんな時に発生しやすいのか確認してみます。その際、発生する待機イベントってなんだろう? (もうわかってるでしょ、でしょ?)

つづく

”SSD”、”SSD”、ってプロジェクトでお金握ってる人の耳元で毎日つぶやいていれば、

いつの日か、「あ、そうだ! SSDがあるじゃまいか」と、自分から言うようになるんじゃないかなと思っているんだが、だれか実践してくれないかなw 


バックナンバー

db file scattered read と db file parallel read と db file sequential read (その1)
db file scattered read と db file parallel read と db file sequential read (その2)
db file scattered read と db file parallel read と db file sequential read (その3)
db file scattered read と db file parallel read と db file sequential read (その4)
db file scattered read と db file parallel read と db file sequential read (その5)

| | コメント (0) | トラックバック (0)

2013年4月13日 (土)

db file scattered read と db file parallel read と db file sequential read (その5)

続きです。

@yoheiaさんがprefetch的な動き...と言っていますね。気になる気になる単語です :)

こことか、
http://www.oracle.com/technetwork/jp/content/007-087711-ja.html

ここなどにもちょろっと書かれていいます。
TANEL PODER'S BLOG: Advanced Oracle Troubleshooting Guide – Part 10: Index unique scan doing multiblock reads?!

マニュアルで記載されている箇所ってあるんでしたっけ? (誰となく..

v$sesstatから該当セッションのセッション統計から"phsical read"または"prefetch"を含む統計値を見みてみると...

SQL文の実行前と実行後を比較するとどの統計値が増加したか一目瞭然です。赤字部分にちゅうもーく!

Oracle® Databaseリファレンス11g リリース2 (11.2)を見てみると、

physical reads cache prefetch
- 事前にフェッチされた連続および不連続ブロックの数 / class - 8 : cache


physical reads prefetch warmup
- バッファ・キャッシュの自動プリウォーム中にディスクから読み取られたデータ・ブロックの数 / class - 8 : cache

と記載されています。どちらもprefetchと関連がありそうですね:)

physical reads cache prefetchの説明にもありますが、連続及び不連続ブロックという部分、待機イベントにも現れていますよね。

連続ブロックを物理読み込みする待機イベントと言えば、db file scatterd readですし、
不連続ブロックを一括読み込みするといえば、 db file parallel readですよね。:)

※SQL文実行前

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
index crx upgrade (prefetch) 0
physical read IO requests 48
physical read bytes 393216
physical read flash cache hits 0
physical read requests optimized 0
physical read total IO requests 48
physical read total bytes 393216
physical read total multi block requests 0
physical reads 48
physical reads cache 48
physical reads cache prefetch 0
physical reads direct 0
physical reads direct (lob) 0
physical reads direct temporary tablespace 0
physical reads for flashback new 0
physical reads prefetch warmup 0
physical reads retry corrupt 0
prefetch clients - 16k 0
prefetch clients - 2k 0
prefetch clients - 32k 0
prefetch clients - 4k 0
prefetch clients - 8k 0
prefetch clients - default 0
prefetch clients - keep 0
prefetch clients - recycle 0
prefetch warmup blocks aged out before use 0
prefetch warmup blocks flushed out before use 0
prefetched blocks aged out before use 0


※SQL文実行後

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
index crx upgrade (prefetch) 0
physical read IO requests 4141
physical read bytes 42541056
physical read flash cache hits 0
physical read requests optimized 0
physical read total IO requests 4141
physical read total bytes 42541056
physical read total multi block requests 34
physical reads 5193
physical reads cache 5193
physical reads cache prefetch 4599
physical reads direct 0
physical reads direct (lob) 0
physical reads direct temporary tablespace 0
physical reads for flashback new 0
physical reads prefetch warmup 1052
physical reads retry corrupt 0
prefetch clients - 16k 0
prefetch clients - 2k 0
prefetch clients - 32k 0
prefetch clients - 4k 0
prefetch clients - 8k 0
prefetch clients - default 0
prefetch clients - keep 0
prefetch clients - recycle 0
prefetch warmup blocks aged out before use 0
prefetch warmup blocks flushed out before use 0
prefetched blocks aged out before use 0


ところで、prefetchもprefetch warmupも物理読み込みなのは分かりましたが、どのような状況だと発生するのでしょう? もしくは発生しやすいのでしょう?

そのヒントはその1に..

次回へつづく




バックナンバー

db file scattered read と db file parallel read と db file sequential read (その1)
db file scattered read と db file parallel read と db file sequential read (その2)
db file scattered read と db file parallel read と db file sequential read (その3)
db file scattered read と db file parallel read と db file sequential read (その4)

| | コメント (0) | トラックバック (0)

2013年4月 8日 (月)

db file scattered read と db file parallel read と db file sequential read (その4)

db file scattered read と db file parallel read と db file sequential read の続きです。

再びリクエストにお答えしてw、SQLとレースを有効した状態でstraceして該当待機イベントでどのようなsystem callになっているのか確認してみたよ > @yoheia :)


db file sequential read

WAIT #4: nam='db file sequential read' ela= 53584 file#=7 block#=375476 blocks=1 obj#=82773 tim=1365258291285100

....
pread64(17, "\6\242\0\0\372\267\5\0\\]\216\0\0\0\1\6\226y\0\0\1\0\0\0UC\1\0009]\216\0"..., 8192, 3070181376) = 8192

db file scattered read

WAIT #4: nam='db file scattered read' ela= 14462 file#=7 block#=374752 blocks=26 obj#=82773 tim=1365258291450333

....
pread64(17, "\6\242\0\0\340\267\5\0005]\216\0\0\0\1\6}\372\0\0\1\0\0\0UC\1\0002]\216\0"..., 212992, 3069968384) = 212992

db file parallel read

WAIT #4: nam='db file parallel read' ela= 25040 files=1 blocks=11 requests=11 obj#=82773 tim=1365258291641614

....
io_submit(1261568, 11, {{0x7981c8, 0, 0, 0, 17}, {0x79834c, 0, 0, 0, 17}, {0x7984d0, 0, 0, 0, 17}, {0x798654, 0, 0, 0, 17}, {0x7987d8, 0, 0, 0, 17}, {0x79895c, 0, 0, 0, 17}, {0x798ae0, 0, 0, 0, 17}, {0x798c64, 0, 0, 0, 17}, {0x798de8, 0, 0, 0, 17}, {0x798f6c, 0, 0, 0, 17}, {0x7990f0, 0, 0, 0, 17}}) = 11
io_getevents(1261568, 11, 128, {{0x79834c, 0x79834c, 8192, 0}, {0x7984d0, 0x7984d0, 8192, 0}, {0x7981c8, 0x7981c8, 8192, 0}, {0x798654, 0x798654, 8192, 0}, {0x7987d8, 0x7987d8, 8192, 0}, {0x798ae0, 0x798ae0, 8192, 0}, {0x798c64, 0x798c64, 8192, 0}, {0x798de8, 0x798de8, 8192, 0}, {0x7990f0, 0x7990f0, 8192, 0}, {0x798f6c, 0x798f6c, 8192, 0}, {0x79895c, 0x79895c, 8192, 0}}, {600, 0}) = 11

このdb file parallel readも狙って出せるくらいだから、たまたまそういう状況では発生しちゃうわけですよ。

以下のSQLトレースは、全てHIGH_CLUSTERING_FACTOR(表)に対する待機イベントですが、何が起きているか想像するとワクワクしてきませんか? (変態だな)

WAIT #4: nam='db file parallel read' ela= 10307 files=1 blocks=14 requests=14 obj#=82773 tim=1364725159669764 
WAIT #4: nam='db file scattered read' ela= 1931 file#=7 block#=374755 blocks=29 obj#=82773 tim=1364725159671819
WAIT #4: nam='db file parallel read' ela= 5874 files=1 blocks=14 requests=14 obj#=82773 tim=1364725159733551 
WAIT #4: nam='db file sequential read' ela= 337 file#=7 block#=374065 blocks=1 obj#=82773 tim=1364725159733959 

ということで、いろいろと、力尽きたので今日はここまで。

次回へつづく。


バックナンバー

db file scattered read と db file parallel read と db file sequential read (その1)
db file scattered read と db file parallel read と db file sequential read (その2)
db file scattered read と db file parallel read と db file sequential read (その3)

| | コメント (0) | トラックバック (0)

2013年4月 4日 (木)

db file scattered read と db file parallel read と db file sequential read (その3)

続きです。

リクエストにお答えして生SQLトレース(抜粋)を見てみましょう :)

最初に実行したSQL文のSQLトレースを見てみることにします。

select
/*+
leading(t2 t1)
use_nl(t2 t1)
index(t2 pk_high_clustering_factor)
*/
t2.id
,t2.name
,t1.name
from
low_clustering_factor t1
inner join high_clustering_factor t2
on
t1.id = t2.id
where
t2.id between 30001 and 35000
/

実行計画は以下の通り、index range/unique scan + nested loop joinが行われていました。

Rows     Row Source Operation
------- ---------------------------------------------------
2421 NESTED LOOPS (cr=5252 pr=2841 pw=0 time=2771706 us)
2421 NESTED LOOPS (cr=2831 pr=2522 pw=0 time=4204941 us cost=5012 size=1530612 card=2501)
2421 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2589 pr=2458 pw=0 time=4188001 us cost=2510 size=765612 card=2502)
2421 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=168 pr=121 pw=0 time=7663 us cost=8 size=0 card=2502)(object id 82774)
2421 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=242 pr=64 pw=0 time=0 us cost=0 size=0 card=1)(object id 82772)
2421 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2421 pr=319 pw=0 time=0 us cost=1 size=306 card=1)

待機イベント見てみると、index range/unique scan + table access by index rowidという実行計画からは想像できない待機イベントが....(実は狙って発生させてるくせに〜w

Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 163 0.00 0.00
db file scattered read 107 0.01 0.30
SQL*Net message from client 163 0.96 3.36
db file parallel read 161 0.01 0.63
SQL*Net more data to client 161 0.00 0.00
db file sequential read 14 0.00 0.00
********************************************************************************

(生)SQLトレース抜粋

...中略...
PARSE #4:c=1000,e=1534,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=1087412721,tim=1364725159601671
EXEC #4:c=1000,e=97,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=1087412721,tim=1364725159601925
WAIT #4: nam='SQL*Net message to client' ela= 7 driver id=1650815232 #bytes=1 p3=0 obj#=82773 tim=1364725159602105 
WAIT #4: nam='db file scattered read' ela= 19499 file#=7 block#=371968 blocks=32 obj#=82774 tim=1364725159621743
WAIT #4: nam='db file scattered read' ela= 11156 file#=7 block#=373703 blocks=32 obj#=82774 tim=1364725159633042
WAIT #4: nam='db file scattered read' ela= 1699 file#=7 block#=375476 blocks=32 obj#=82773 tim=1364725159635032
WAIT #4: nam='db file scattered read' ela= 5889 file#=7 block#=367232 blocks=32 obj#=82772 tim=1364725159641300
WAIT #4: nam='db file scattered read' ela= 17223 file#=7 block#=368599 blocks=32 obj#=82771 tim=1364725159658667
FETCH #4:c=2999,e=56653,p=160,cr=6,cu=0,mis=0,r=1,dep=0,og=1,plh=1087412721,tim=1364725159658788
WAIT #4: nam='SQL*Net message from client' ela= 430 driver id=1650815232 #bytes=1 p3=0 obj#=82771 tim=1364725159659311 
WAIT #4: nam='db file parallel read' ela= 10307 files=1 blocks=14 requests=14 obj#=82773 tim=1364725159669764 
WAIT #4: nam='db file scattered read' ela= 1931 file#=7 block#=374755 blocks=29 obj#=82773 tim=1364725159671819 
WAIT #4: nam='SQL*Net message to client' ela= 3 driver id=1650815232 #bytes=1 p3=0 obj#=82773 tim=1364725159671902 
WAIT #4: nam='SQL*Net more data to client' ela= 40 driver id=1650815232 #bytes=8330 p3=0 obj#=82773 tim=1364725159672223
FETCH #4:c=2000,e=12910,p=43,cr=38,cu=0,mis=0,r=15,dep=0,og=1,plh=1087412721,tim=1364725159672272
WAIT #4: nam='SQL*Net message from client' ela= 9227 driver id=1650815232 #bytes=1 p3=0 obj#=82773 tim=1364725159681536 
WAIT #4: nam='db file parallel read' ela= 5766 files=1 blocks=12 requests=12 obj#=82773 tim=1364725159687404 
WAIT #4: nam='db file scattered read' ela= 1922 file#=7 block#=376416 blocks=32 obj#=82773 tim=1364725159689681
WAIT #4: nam='SQL*Net message to client' ela= 3 driver id=1650815232 #bytes=1 p3=0 obj#=82773 tim=1364725159689951 
WAIT #4: nam='SQL*Net more data to client' ela= 13 driver id=1650815232 #bytes=8278 p3=0 obj#=82773 tim=1364725159690473
FETCH #4:c=4000,e=8972,p=44,cr=32,cu=0,mis=0,r=15,dep=0,og=1,plh=1087412721,tim=1364725159690547
WAIT #4: nam='SQL*Net message from client' ela= 7980 driver id=1650815232 #bytes=1 p3=0 obj#=82773 tim=1364725159698617 
WAIT #4: nam='db file parallel read' ela= 2716 files=1 blocks=13 requests=13 obj#=82773 tim=1364725159701431 
WAIT #4: nam='db file scattered read' ela= 2179 file#=7 block#=374240 blocks=32 obj#=82773 tim=1364725159703739 
WAIT #4: nam='SQL*Net message to client' ela= 5 driver id=1650815232 #bytes=1 p3=0 obj#=82773 tim=1364725159704315 
WAIT #4: nam='SQL*Net more data to client' ela= 40 driver id=1650815232 #bytes=8278 p3=0 obj#=82773 tim=1364725159704541 
FETCH #4:c=3999,e=5937,p=45,cr=32,cu=0,mis=0,r=15,dep=0,og=1,plh=1087412721,tim=1364725159704589
WAIT #4: nam='SQL*Net message from client' ela= 11759 driver id=1650815232 #bytes=1 p3=0 obj#=82773 tim=1364725159716396 
WAIT #4: nam='db file parallel read' ela= 1999 files=1 blocks=13 requests=13 obj#=82773 tim=1364725159718604 
WAIT #4: nam='db file scattered read' ela= 1667 file#=7 block#=374066 blocks=32 obj#=82773 tim=1364725159720394 
WAIT #4: nam='SQL*Net message to client' ela= 3 driver id=1650815232 #bytes=1 p3=0 obj#=82773 tim=1364725159720497 
WAIT #4: nam='SQL*Net more data to client' ela= 43 driver id=1650815232 #bytes=8278 p3=0 obj#=82773 tim=1364725159720707 
FETCH #4:c=1000,e=4261,p=45,cr=32,cu=0,mis=0,r=15,dep=0,og=1,plh=1087412721,tim=1364725159720752
WAIT #4: nam='SQL*Net message from client' ela= 6700 driver id=1650815232 #bytes=1 p3=0 obj#=82773 tim=1364725159727511 
WAIT #4: nam='db file parallel read' ela= 5874 files=1 blocks=14 requests=14 obj#=82773 tim=1364725159733551 
WAIT #4: nam='db file sequential read' ela= 337 file#=7 block#=374065 blocks=1 obj#=82773 tim=1364725159733959 
WAIT #4: nam='SQL*Net message to client' ela= 4 driver id=1650815232 #bytes=1 p3=0 obj#=82773 tim=1364725159734041 
WAIT #4: nam='SQL*Net more data to client' ela= 77 driver id=1650815232 #bytes=8278 p3=0 obj#=82773 tim=1364725159734271 
...以下略...


db file * read待機イベントとアクセスブロック数及び参照オブジェクトとオブジェクト名を見やすくすると以下のようになります。
db_file_multiblock_read_count=32なのでscatterd readのblocksは理解できるのですが、parallel readのblocksってどこで制御してるんだ..わからんw..というか調べきれてない..

db file scattered read  blocks=32 (INDEX:PK_HIGH_CLUSTERING_FACTOR)
db file scattered read blocks=32 (INDEX:PK_HIGH_CLUSTERING_FACTOR)
db file scattered read blocks=32 (TABLE:HIGH_CLUSTERING_FACTOR)
db file scattered read blocks=32 (INDEX:PK_LOW_CLUSTERING_FACTOR)
db file scattered read blocks=32 (TABLE:LOW_CLUSTERING_FACTOR)
db file parallel read blocks=14 (TABLE:HIGH_CLUSTERING_FACTOR)
db file scattered read blocks=29 (TABLE:HIGH_CLUSTERING_FACTOR)
db file parallel read blocks=12 (TABLE:HIGH_CLUSTERING_FACTOR)
db file scattered read blocks=32 (TABLE:HIGH_CLUSTERING_FACTOR)
db file parallel read blocks=13 (TABLE:HIGH_CLUSTERING_FACTOR)
db file scattered read blocks=32 (TABLE:HIGH_CLUSTERING_FACTOR)
db file parallel read blocks=13 (TABLE:HIGH_CLUSTERING_FACTOR)
db file scattered read blocks=32 (TABLE:HIGH_CLUSTERING_FACTOR)
db file parallel read blocks=14 (TABLE:HIGH_CLUSTERING_FACTOR)
db file sequential read blocks=1 (TABLE:HIGH_CLUSTERING_FACTOR)

発生しているdb file * read待機イベントでアクセスしているオブジェクトは以下の通り。
実行計画上 index unique/range scan なのですが、索引でもscatterd readが発生することがあるんですよ〜っ。

db file parallel read
- obj# = 82773 - HIGH_CLUSTERING_FACTOR(表)

db file scattered read
- obj# = 82771 - LOW_CLUSTERING_FACTOR(表)
- obj# = 82772 - PK_LOW_CLUSTERING_FACTOR(索引)
- obj# = 82773 - HIGH_CLUSTERING_FACTOR(表)
- obj# = 82774 - PK_HIGH_CLUSTERING_FACTOR(索引)

db file sequential read
- obj# = 82771 - LOW_CLUSTERING_FACTOR(表)
- obj# = 82773 - HIGH_CLUSTERING_FACTOR(表)


2つ目のクエリは db file parallel readが発生しないだけで傾向は同じです。 :)

次回へつづく。


バックナンバー

db file scattered read と db file parallel read と db file sequential read (その1)
db file scattered read と db file parallel read と db file sequential read (その2)

| | コメント (0) | トラックバック (0)

2013年4月 1日 (月)

db file scattered read と db file parallel read と db file sequential read (その2)

答え分かりましたかねぇ?

環境情報など再現方法などは次回にしますが、どうなるかやってみましょう。(事前にSQLトレースを有効にしてあります。)


※各SQLはヒントで実行計画を固定してあります。
最初は参考資料3の実行計画になるクエリから実行します。

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2501 | 1494K| 5012 (1)| 00:01:01 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 2501 | 1494K| 5012 (1)| 00:01:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| HIGH_CLUSTERING_FACTOR | 2502 | 747K| 2510 (1)| 00:00:31 |
|* 4 | INDEX RANGE SCAN | PK_HIGH_CLUSTERING_FACTOR | 2502 | | 8 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | PK_LOW_CLUSTERING_FACTOR | 1 | | 0 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID | LOW_CLUSTERING_FACTOR | 1 | 306 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

SELECT
/*+
leading(t2 t1)
use_nl(t2 t1)
index(t2 pk_high_clustering_factor)
*/
t2.id
,t2.name
,t1.name
FROM
low_clustering_factor t1
INNER JOIN high_clustering_factor t2
ON
t1.id = t2.id
WHERE
t2.id BETWEEN 30001 AND 35000
/

次は参考資料2の実行計画になるクエリを実行します。

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2500 | 1494K| 5231 (1)| 00:01:03 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 2500 | 1494K| 5231 (1)| 00:01:03 |
| 3 | TABLE ACCESS BY INDEX ROWID| LOW_CLUSTERING_FACTOR | 5000 | 1494K| 229 (0)| 00:00:03 |
|* 4 | INDEX RANGE SCAN | PK_LOW_CLUSTERING_FACTOR | 5000 | | 11 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | PK_HIGH_CLUSTERING_FACTOR | 1 | | 0 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID | HIGH_CLUSTERING_FACTOR | 1 | 306 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

SELECT
/*+
leading(t1 t2)
use_nl(t1 t2)
index(t1 pk_low_clustering_factor)
*/
t1.id
,t1.name
,t2.name
FROM
low_clustering_factor t1
INNER JOIN high_clustering_factor t2
ON
t1.id = t2.id
WHERE
t1.id BETWEEM 1 AND 5000
/

どのような待機イベントが発生しているかSQLトレース結果(tkprofで整形済み)を見てみると...

最初のクエリのトレース結果

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 163 0.30 1.06 2841 5252 0 2421
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 165 0.30 1.06 2841 5252 0 2421

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84

Rows Row Source Operation
------- ---------------------------------------------------
2421 NESTED LOOPS (cr=5252 pr=2841 pw=0 time=2771706 us)
2421 NESTED LOOPS (cr=2831 pr=2522 pw=0 time=4204941 us cost=5012 size=1530612 card=2501)
2421 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2589 pr=2458 pw=0 time=4188001 us cost=2510 size=765612 card=2502)
2421 INDEX RANGE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=168 pr=121 pw=0 time=7663 us cost=8 size=0 card=2502)(object id 82774)
2421 INDEX UNIQUE SCAN PK_LOW_CLUSTERING_FACTOR (cr=242 pr=64 pw=0 time=0 us cost=0 size=0 card=1)(object id 82772)
2421 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=2421 pr=319 pw=0 time=0 us cost=1 size=306 card=1)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 163 0.00 0.00
db file scattered read 107 0.01 0.30
SQL*Net message from client 163 0.96 3.36
db file parallel read 161 0.01 0.63
SQL*Net more data to client 161 0.00 0.00
db file sequential read 14 0.00 0.00

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 173 0.28 0.72 1874 3367 0 2566
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 175 0.28 0.73 1874 3367 0 2566

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84

Rows Row Source Operation
------- ---------------------------------------------------
2566 NESTED LOOPS (cr=3367 pr=1874 pw=0 time=802588 us)
2566 NESTED LOOPS (cr=801 pr=381 pw=0 time=115791 us cost=5231 size=1530000 card=2500)
5000 TABLE ACCESS BY INDEX ROWID LOW_CLUSTERING_FACTOR (cr=564 pr=286 pw=0 time=42241 us cost=229 size=1530000 card=5000)
5000 INDEX RANGE SCAN PK_LOW_CLUSTERING_FACTOR (cr=181 pr=32 pw=0 time=26744 us cost=11 size=0 card=5000)(object id 82772)
2566 INDEX UNIQUE SCAN PK_HIGH_CLUSTERING_FACTOR (cr=237 pr=95 pw=0 time=0 us cost=0 size=0 card=1)(object id 82774)
2566 TABLE ACCESS BY INDEX ROWID HIGH_CLUSTERING_FACTOR (cr=2566 pr=1493 pw=0 time=0 us cost=1 size=306 card=1)


Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
SQL*Net message to client 173 0.00 0.00
db file scattered read 381 0.01 0.42
db file sequential read 287 0.00 0.14
SQL*Net message from client 172 0.04 2.83
SQL*Net more data to client 171 0.00 0.00


ということで、

A) db file scattered read
B) db file parallel read
C) db file sequential read

A)、B)、C)すべての待機イベント発生しました!!! :)  

実に興味深い。。。。 実行計画見てても、分からないですよね、こればかりは。


次回へ続く。




バックナンバー
db file scattered read と db file parallel read と db file sequential read (その1)

| | コメント (0) | トラックバック (0)

2013年3月30日 (土)

db file scattered read と db file parallel read と db file sequential read (その1)

突然ですが、問題です!

・Oracle11g R2です。(隠しパラメータは変更していません)
・Oracleインスタンスは起動したばかりです。
・参考資料1)のような索引が、それぞれの表に作成されています。
・駆動表は入れ替わっていますが、参考資料2)と3)のような実行計画で2つのSQL文が実行されました。

発生する待機イベントはどれでしょうか?

A) db file scattered read
B) db file parallel read
C) db file sequential read

参考資料1)

TABLE_NAME                     INDEX_NAME                       NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
------------------------------ ------------------------------ ---------- ------------- -----------------
HIGH_CLUSTERING_FACTOR PK_HIGH_CLUSTERING_FACTOR 100000 100000 99978
LOW_CLUSTERING_FACTOR PK_LOW_CLUSTERING_FACTOR 100000 100000 4348

参考資料2)

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2500 | 1494K| 5231 (1)| 00:01:03 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 2500 | 1494K| 5231 (1)| 00:01:03 |
| 3 | TABLE ACCESS BY INDEX ROWID| LOW_CLUSTERING_FACTOR | 5000 | 1494K| 229 (0)| 00:00:03 |
|* 4 | INDEX RANGE SCAN | PK_LOW_CLUSTERING_FACTOR | 5000 | | 11 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | PK_HIGH_CLUSTERING_FACTOR | 1 | | 0 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID | HIGH_CLUSTERING_FACTOR | 1 | 306 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

参考資料3)

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2501 | 1494K| 5012 (1)| 00:01:01 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 2501 | 1494K| 5012 (1)| 00:01:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| HIGH_CLUSTERING_FACTOR | 2502 | 747K| 2510 (1)| 00:00:31 |
|* 4 | INDEX RANGE SCAN | PK_HIGH_CLUSTERING_FACTOR | 2502 | | 8 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | PK_LOW_CLUSTERING_FACTOR | 1 | | 0 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID | LOW_CLUSTERING_FACTOR | 1 | 306 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

次回へつづく.... :) Enjoy!

| | コメント (0) | トラックバック (0)

2012年12月25日 (火)

Indexがあれば、いいってもんじゃない。中身がたいせつ

吉田さんが、なぜ24日を選んだのか理由がわかった...


さて、JPOUG Advent Calendar、25番目の扉を開けます。

ネタは、そう、やはり、索引です。:)


以下のようなバッチ処理があったと思ってくださいな。

リリース直後、データが少ない状態で統計情報をロックしたまま数年が経過したバッチ処理、リリース当初は特に目立った遅延問題も発生せず安定していた(らしい)。

ところが、ところがです。
最近は、少しでも処理量が増加しようものなら100%遅延するという状況となっていた。

関係者へのヒアリングから、どうやら、グルグル系のバッチであることが見えてきました。(またか!w)

AWRレポートから問題となるSQL文(グルグル回るところが一番怪しいんですけどね、大抵は)を特定しました。(以下参照)

Execution Plan
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 843 (100)| |
| 1 | SORT AGGREGATE | | 1 | 19 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| HOGE | 1 | 19 | 843 (1)| 00:00:11 |
| 3 | INDEX RANGE SCAN | HOGE_I01 | 1 | | 842 (1)| 00:00:11 |
-----------------------------------------------------------------------------------------

Full SQL Text

SQL ID SQL Text
------------ -----------------------------------------------------------------
43a7z10a4ckx SELECT COUNT(1) FROM HOGE WHERE
CATEGORY = :B4 AND CLASS = :B3 AND SUBCLASS = :B2 AND ID = :B5
AND SUBID = :B1 AND IS_DELETED = 0

実行計画だけを見ていると、Index Range Scan?、なにが問題なの?(キリっ という感じがしますが、
それに騙されてはいけいけないんです。

次のセクションを見てください。
(本来10万回実行されるのですが終わらないので途中でキャンセルしましたw、約1時間で5000回も回ってません。)

Plan 1(PHV: 468154321)
----------------------

Plan Statistics DB/Inst: DISCUS/discus Snaps: 129-130
-> % Total DB Time is the Elapsed Time of the SQL statement divided
into the Total Database Time multiplied by 100

Stat Name Statement Per Execution % Snap
---------------------------------------- ---------- -------------- -------
Elapsed Time (ms) 3,185,031 640.9 99.8
CPU Time (ms) 3,092,467 622.2 99.8
Executions 4,970 N/A N/A
Buffer Gets 7.7276E+07 15,548.5 99.9
Disk Reads 13,999 2.8 93.4
Parse Calls 1 0.0 0.0
Rows 4,969 1.0 N/A
User I/O Wait Time (ms) 92,540 N/A N/A
Cluster Wait Time (ms) 0 N/A N/A
Application Wait Time (ms) 0 N/A N/A
Concurrency Wait Time (ms) 0 N/A N/A
Invalidations 0 N/A N/A
Version Count 1 N/A N/A
Sharable Mem(KB) 22 N/A N/A
-------------------------------------------------------------

Index Range Scanで平均Rows=1の割にBuffer Getsが異常に多い、処理時間のほとんどがCPU処理時間。

これらがなにを意味するのか............

確認のためexplain planで取得した実行計画のPredicate Informationを見てみると..

Plan hash value: 468154321

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 19 | 795 (1)| 00:00:10 |
| 1 | SORT AGGREGATE | | 1 | 19 | | |
|* 2 | TABLE ACCESS BY INDEX ROWID| HOGE | 1 | 19 | 795 (1)| 00:00:10 |
|* 3 | INDEX RANGE SCAN | HOGE_I01 | 1 | | 794 (1)| 00:00:10 |
-----------------------------------------------------------------------------------------

PLAN_TABLE_OUTPUT
---------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("IS_DELETED"=0)
3 - access("CATEGORY"=TO_NUMBER(:V1) AND "CLASS"=TO_NUMBER(:V2) AND
"SUBCLASS"=TO_NUMBER(:V3) AND "ID"=TO_NUMBER(:V4) AND "SUBID"=TO_NUMBER(:V5))
filter("ID"=TO_NUMBER(:V4) AND "SUBID"=TO_NUMBER(:V5))

索引アクセス時にID列とSUBID列でフィルタ処理が行われています。:) この部分の比率が非常に大きい。
ようするに大量のデータを読み込み、フィルタ処理でそのほとんどを捨てている!!! 
(もったいない、もったいないです。もったいないお化けが出てきそうです)

索引を見てみます。

INDEX_NAME                     COLUMN_NAME
------------------------------ --------------------
HOGE_I01 CATEGORY
HOGE_I01 CLASS
HOGE_I01 SUBCLASS
HOGE_I01 STATUS
HOGE_I01 ID
HOGE_I01 SUBID

索引の先頭3列(CATEGORY,CLASS,SUBCLASS)でデータ数をカウントしてみると、400,000行ありました...
40万行から、たった1行を取り出すフィルタリングを10万回繰り返している...グルグル系恐るべし(@@)


解決方法は....

索引の列順を入れ替えれば解決するはず!!!
status列はカーディナリティが非常に低い列であることは確認ずみであつたため最後尾に移動しました。
先頭5列(CATEGORY,CLASS,SUBCLASS,ID,SUBID)でほぼデータが絞りこめるようになるはずです。
(AWRレポートのRowsを見ると最終的には1行or0行となることが確認できます)

作り直した索引は以下のようになります。
STATUS列が最後になっているところがポイントです。違いはたったそれだけです。
(Index Only Accessではありませんよ、今回は。Index Only Accessでグルグルをギリギリまでチューニングしなくても十分な効果が得られたのでこれ以上なにもしません。)

INDEX_NAME                     COLUMN_NAME
------------------------------ --------------------
HOGE_I02 CATEGORY
HOGE_I02 CLASS
HOGE_I02 SUBCLASS
HOGE_I02 ID
HOGE_I02 SUBID
HOGE_I02 STATUS


索引を作り直した後はご覧の通り、じゃじゃ〜〜〜〜〜〜ん!
想定17時間程度のダメバッチが30分ほどで終了しました。ニッコリ。CPUにも随分優しくなってますね。

Plan 1(PHV: 2833120447)
-----------------------

Plan Statistics DB/Inst: DISCUS/discus Snaps: 131-132
-> % Total DB Time is the Elapsed Time of the SQL statement divided
into the Total Database Time multiplied by 100

Stat Name Statement Per Execution % Snap
---------------------------------------- ---------- -------------- -------
Elapsed Time (ms) 1,846,702 18.5 99.2
CPU Time (ms) 43,208 0.4 89.1
Executions 100,000 N/A N/A
Buffer Gets 3,971,771 39.7 99.1
Disk Reads 290,822 2.9 99.1
Parse Calls 1 0.0 0.0
Rows 100,000 1.0 N/A
User I/O Wait Time (ms) 1,830,618 N/A N/A
Cluster Wait Time (ms) 0 N/A N/A
Application Wait Time (ms) 0 N/A N/A
Concurrency Wait Time (ms) 0 N/A N/A
Invalidations 0 N/A N/A
Version Count 1 N/A N/A
Sharable Mem(KB) 22 N/A N/A
-------------------------------------------------------------

Execution Plan
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 5 (100)| |
| 1 | SORT AGGREGATE | | 1 | 19 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| HOGE | 1 | 19 | 5 (0)| 00:00:01 |
| 3 | INDEX RANGE SCAN | HOGE_I02 | 1 | | 4 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

Full SQL Text

SQL ID SQL Text
------------ -----------------------------------------------------------------
43a7z10a4ckx SELECT COUNT(1) FROM HOGE WHERE CATEGORY = :B4 AND CLASS = :B3 AN
D SUBCLASS = :B2 AND ID = :B5 AND SUBID = :B1 AND IS_DELETED = 0


念のためexplain plan でも確認してみます。ご覧の通りフィルタリングはis_deleted列のみでindex range scan時のフィルタリングは全く発生していません。

もったいないお化けを退治できましたv

Plan hash value: 2833120447

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 19 | 5 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 19 | | |
|* 2 | TABLE ACCESS BY INDEX ROWID| HOGE | 1 | 19 | 5 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | HOGE_I02 | 1 | | 4 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

PLAN_TABLE_OUTPUT
---------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("IS_DELETED"=0)
3 - access("CATEGORY"=TO_NUMBER(:B4) AND "CLASS"=TO_NUMBER(:B3) AND
"SUBCLASS"=TO_NUMBER(:B2) AND "ID"=TO_NUMBER(:B5) AND "SUBID"=TO_NUMBER(:B1))

複合索引の列順って、ほんとうに大切ですよね :)

NOTE:
今回のような遅延ケースでは索引を適切に再構成するほかに、ヒントでIndex skip scanを強制する方法もありますが、索引を適切な列の並びに再構成する方法と比べ劣るのも事実です。
大人の事情に金縛りに合うこともあるでしょうし.....。状況に合わせて使い分けるとよいと思います。

これで、JPOUG Advent Calendarの全ての小窓は開かれました。

メリークリスマス、そして、良いお年をお迎えください。


最後に、Pipelined Table Function で Christmas Tree ☆☆☆☆

 

| | コメント (0) | トラックバック (0)

2012年10月21日 (日)

Unconference at db tech showcase 2012の資料公開 :)

db tech showcase 2012
Unconference at db tech showcase 2012

db tech showcaseの一角をJPOUGが占拠してUnconferenceを開催しました。 db tech showcase関係者の皆様、このような機会を与えて頂き大変感謝します。
そして、お疲れさまでした。



Index Only Access 3部作の最終回?! として 「Index Only Accessが実装されるたった一つの理由」と題したセッションを行いました。
実行計画を取得するために操作したデータベースの中には人生二度目のデータベース複数もあり、かなりの時間を裂いて調べた割にはセッション時間が少々短めになってしまいました。m(_ _)m

なぜ、このテーマを選んだか.

PostgreSQLがリリースされてから9.1まで実装されなかったIndex Only Accessでしたが、9.2でついに実装されました。

そして..db tech showcase 2012は...

SQL> select dbms_name from all_dbms where dbms_category like "%";

DBMS_NAME
------------------
Oracle
DB2
MySQL
PostgreSQL
SQL Server
Vecterwise
MongoDB
Symfoware
Clustrix
InfiniDB
.
.
.
.

的な雰囲気となっていることもあり、Index Only Accessの魚拓をあつめて比較、Index Only Accessが実装される理由について今一度、考えてみたいな..と。
タイトル見ただけで理由が想像できた方は、資料見なくても大丈夫だと思いますよ。:) 
 

H/Wの性能が急速に伸びてきている影響もあるように感じますが、無駄に広範囲な検索や、無駄にビッグなデータとなっていること気にしていないのではないか? というケースが多くなっていると感じています。
DBMSはアクセスするデータをより少なくするための工夫をしているのに...エンジニアがそれをうまく使っていない、使えていない、設計できてない...そんな"感じ"がするんです。

セッション資料を公開しました。
S1a


じゃ、like "%てない" 状況をどうすればいいか....答えは、小田さんのセッションの中にあった。。。。:)


#不慣れなDBMSもあり、こんなメトリックみたほうが分かりやすいよ〜、などのツッコミ歓迎します.


| | コメント (0) | トラックバック (0)

2012年9月 2日 (日)

Index Only Accessネタのおまけのおまけ

Caché de Index Only Access できるのか?

CachéってSQLもサポートしてるので、Index Only Accessサポートしてるのかな〜と素朴な疑問から調べてみたもののマニュアルには記載されていない。
書かれていなきゃどうなるか、試すのが手っ取り早いですね。
(ダウンロードするにはフォームからリクエストする必要があります。1セッション限定の試用ライセンスのようです。他の制限は該当ページ読んでくださいな。)


OracleのIndex Only Accessは以下のエントリを参考にしてください。
いん!、イン!、Index どっぷり Inde Only Access生活w - Oracle OpenWorld Unconference presented by JPOUG
JPOUG SET EVENTS 20120721 - 「(続)いん!、イン!、Index 大人の事情縛りのSQLチューニング」資料公開

MySQL/PostgreSQLのIndex Only Accessは以下のエントリを参考にしてください。
Index Only Accessネタのおまけ


いつもはMacBook Air使うんですが、今回は都合により母艦のVirtualBoxを使いました (^^;;

MacPro Mid 2012 12Core/24GB (OS X Mountain Lion Server)
VirtualBox4.1.20 for MacOS X
GuestOS:CentOS5.8 x86_64 (CPU数とメモリサイズはMacBook Airのx86環境と同じ)

Caché 2012.1 for Linux x86_64


まず、ターミナルで接続して表、索引、データ登録、他のデータベースで言う統計情報取得から。

[oracle@pleco ˜]$ csession cache -U samples

ノード: pleco.macdeoracle.jp インスタンス: CACHE

SAMPLES>

SAMPLES>d $System.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------

The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
SAMPLES>>
SAMPLES>>create table tab3(unique_id numeric(10) not null, item_code char(15) not null, data varchar(500), is_delete numeric(1), status_code char(2))
1. create table tab3(unique_id numeric(10) not null, item_code char(15) not null, data varchar(500), is_delete numeric(1), status_code char(2))

0 Rows Affected
statement prepare time: 0.0118s, elapsed execute time: 0.2405s.
---------------------------------------------------------------------------
SAMPLES>>
SAMPLES>>alter table tab3 add constraint tab3_pk primary key(unique_id)
3. alter table tab3 add constraint tab3_pk primary key(unique_id)

0 Rows Affected
statement prepare time: 0.0092s, elapsed execute time: 4.9465s.
---------------------------------------------------------------------------
SAMPLES>>
SAMPLES>>create table tab311 (unique_id numeric(10) not null,sub_item_code char(10),data varchar(500),is_delete numeric(1))
4. create table tab311 (unique_id numeric(10) not null,sub_item_code char(10),data varchar(500),is_delete numeric(1))

0 Rows Affected
statement prepare time: 0.0115s, elapsed execute time: 0.1953s.
---------------------------------------------------------------------------
SAMPLES>>create table tab31 (item_code char(15) not null,sub_item_code char(10),data varchar(500),is_delete numeric(1))
5. create table tab31 (item_code char(15) not null,sub_item_code char(10),data varchar(500),is_delete numeric(1))

0 Rows Affected
statement prepare time: 0.0133s, elapsed execute time: 0.1985s.
---------------------------------------------------------------------------
SAMPLES>>
SAMPLES>>alter table tab31 add constraint tab31_pk primary key(item_code)
7. alter table tab31 add constraint tab31_pk primary key(item_code)

0 Rows Affected
statement prepare time: 0.0102s, elapsed execute time: 0.2589s.
---------------------------------------------------------------------------
SAMPLES>>alter table tab311 add constraint tab311_pk primary key(unique_id)
8. alter table tab311 add constraint tab311_pk primary key(unique_id)

0 Rows Affected
statement prepare time: 0.0116s, elapsed execute time: 0.2627s.
---------------------------------------------------------------------------
SAMPLES>>create index tab311_ix on tab311(sub_item_code)
9. create index tab311_ix on tab311(sub_item_code)

0 Rows Affected
statement prepare time: 0.0111s, elapsed execute time: 0.2870s.
---------------------------------------------------------------------------
SAMPLES>>q

確認は管理ポータルで!(どこみればよいか分からなかったので楽な方法で..)

Desc_tab3

Tab3_indexes_no_index_only

Desc_tab31

Tab31_indexes_no_index_only

Desc_tab311

Tab311_indexes_no_index_only_scan

Oracleなら統計情報取得、他のRDBMSならアナライズ(昔はオラクルもアナライズだったんですけどね)、Cachéの世界では「テーブルチューニング」というそうな。
(管理ポータルから実行しています)
Table_analyze

Tab31_analyze

Tab311_analyze

表と索引ができたのでデータ登録。
これまたOracleとは勝っても違うし、DSMの時代(DIGITAL STANDARD MUMPS)から大きく拡張されてるのでかなり辛いな〜MUMPS思い出すというより別物に近い感じw。あの当時はSQL使えねーしというより必要ねーし、だったしね (^^;;;

以下、データ作成中のログ..
グルグル回しているのは気にしないでねw

[oracle@pleco ˜]$ csession cache -U samples

ノード: pleco.macdeoracle.JP インスタンス: CACHE

SAMPLES>
SAMPLES>
SAMPLES>s rs=##class(%SQL.Statement).%ExecDirect(,"start transaction")
TL1:SAMPLES>s sql="insert into SQLUser.tab3 values(?,to_char((?#500000)+1,'FM099999999999999'),repeat('*',250-length(to_char(?)))||to_char(?),0,'00')"
TL1:SAMPLES>s stmt=##class(%SQL.Statement).%New() s stat=stmt.%Prepare(sql)
TL1:SAMPLES>f i=1:1:1000000 {s rs=stmt.%Execute(i,i,i,i)}
TL1:SAMPLES>s rs=##class(%SQL.Statement).%ExecDirect(,"commit")
SAMPLES>d $System.SQL.Shell()

SQL Command Line Shell
----------------------------------------------------

The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
SAMPLES>>select count(1) from tab3
7. select count(1) from tab3

Aggregate_1
1000000

1 Rows(s) Affected
statement prepare time: 0.0009s, elapsed execute time: 2.6558s.
---------------------------------------------------------------------------
SAMPLES>>select min(unique_id) as "min", max(unique_id) as "max" from tab3
8. select min(unique_id) as "min", max(unique_id) as "max" from tab3

min max
1 1000000

1 Rows(s) Affected
statement prepare time: 0.1423s, elapsed execute time: 0.0006s.
---------------------------------------------------------------------------
SAMPLES>>select min(item_code) "min",max(item_code) "max" from tab3
9. select min(item_code) "min",max(item_code) "max" from tab3

min max
000000000000001 000000000500000

1 Rows(s) Affected
statement prepare time: 0.1324s, elapsed execute time: 3.4683s.
---------------------------------------------------------------------------
SAMPLES>>
SAMPLES>>q

SAMPLES>halt
[oracle@pleco ˜]$

SAMPLES>d $System.SQL.SetAutoCommit(2)
SAMPLES>s rs=##class(%SQL.Statement).%ExecDirect(,"start transaction")
TL1:SAMPLES>s sql="insert into SQLUser.tab31 values(to_char(?,'FM099999999999999'),to_char((?#500000)+1,'FM0999999999'),repeat('*',250-length(to_char(?)))||to_char(?),0)"
TL1:SAMPLES>s stmt=##class(%SQL.Statement).%New() s stat=stmt.%Prepare(sql)
TL1:SAMPLES>f i=1:1:2000000 {s rs=stmt.%Execute(i,i,i,i)}
TL1:SAMPLES>s rs=##class(%SQL.Statement).%ExecDirect(,"commit")

SAMPLES>d $System.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------

The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
SAMPLES>>select count(1) from tab31
2. select count(1) from tab31

Aggregate_1
2000000

1 Rows(s) Affected
statement prepare time: 0.1349s, elapsed execute time: 6.0684s.
---------------------------------------------------------------------------
SAMPLES>>select min(item_code) "item_code(MIN)",max(item_code) "item_code(MAX)" from tab31
3. select min(item_code) "item_code(MIN)",max(item_code) "item_code(MAX)" from tab31

item_code(MIN) item_code(MAX)
000000000000001 000000002000000

1 Rows(s) Affected
statement prepare time: 0.1361s, elapsed execute time: 0.0010s.
---------------------------------------------------------------------------
SAMPLES>>select min(sub_item_code) "sub_item_code(MIN)",max(sub_item_code) "sub_item_code(MAX)" from tab31
4. select min(sub_item_code) "sub_item_code(MIN)",max(sub_item_code) "sub_item_code(MAX)" from tab31

sub_item_code(MIN) sub_item_code(MAX)
0000000001 0000500000

1 Rows(s) Affected
statement prepare time: 0.1391s, elapsed execute time: 6.7417s.
---------------------------------------------------------------------------
SAMPLES>>q
SAMPLES>

SAMPLES>d $System.SQL.SetAutoCommit(2)
SAMPLES>s rs=##class(%SQL.Statement).%ExecDirect(,"start transaction")
TL1:SAMPLES>s sql="insert into SQLUser.tab311 values(?,to_char((?#500000)+1,'FM0999999999'),repeat('*',250-length(to_char(?)))||to_char(?),0)"
TL1:SAMPLES>s stmt=##class(%SQL.Statement).%New() s stat=stmt.%Prepare(sql)
TL1:SAMPLES>f i=1:1:2000000 {s rs=stmt.%Execute(i,i,i,i)}
TL1:SAMPLES>s rs=##class(%SQL.Statement).%ExecDirect(,"commit")

SAMPLES>d $System.SQL.Shell()

SQL Command Line Shell
----------------------------------------------------

The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
SAMPLES>>select count(1) from tab311
5. select count(1) from tab311

Aggregate_1
2000000

1 Rows(s) Affected
statement prepare time: 0.0009s, elapsed execute time: 3.7845s.
---------------------------------------------------------------------------
SAMPLES>>select min(sub_item_code) "sub_item_code(MIN)", max(sub_item_code) "sub_item_code(MAX)" from tab311
6. select min(sub_item_code) "sub_item_code(MIN)", max(sub_item_code) "sub_item_code(MAX)" from tab311

sub_item_code(MIN) sub_item_code(MAX)
0000000001 0000500000

1 Rows(s) Affected
statement prepare time: 0.1558s, elapsed execute time: 0.0004s.
---------------------------------------------------------------------------
SAMPLES>>q


本題からはずれるのですが... SQL使ってるとはいっても、ベースはMUMPSというかM言語だからね〜というお話を。。

tab3表及び主キー制約で作成された索引に対応するglobals。索引を見るとSparse Multidimensional Arrayをうまく利用しているのがよくわかる :)
Cachéの表や索引の実態はSparse Multidimensional Arrayなんだお。

1:      ^User.tab3D    =     1512812
2: ^User.tab3D(512813) = $lb("",1,"000000000000002","***************** ...中略... *******************************1",0,"00")
3: ^User.tab3D(512814) = $lb("",2,"000000000000003","***************** ...中略... *******************************2",0,"00")
4: ^User.tab3D(512815) = $lb("",3,"000000000000004","***************** ...中略... *******************************3",0,"00")
...以下略...

tab3表の主キー索引

1:      ^User.tab3I("tab3pk",1,512813)    =     ""
2: ^User.tab3I("tab3pk",2,512814) = ""
3: ^User.tab3I("tab3pk",3,512815) = ""
...以下略...

次は、tab31表と主キー索引

1:      ^User.tab31D    =     2000000
2: ^User.tab31D(1) = $lb("","000000000000001","0000000002","***************** ...中略... *************************1",0)
3: ^User.tab31D(2) = $lb("","000000000000002","0000000003","***************** ...中略... *************************2",0)
4: ^User.tab31D(3) = $lb("","000000000000003","0000000004","***************** ...中略... *************************3",0)
...以下略...


1:      ^User.tab31I("tab31pk"," 000000000000001",1)    =     ""
2: ^User.tab31I("tab31pk"," 000000000000002",2) = ""
3: ^User.tab31I("tab31pk"," 000000000000003",3) = ""
...以下略...


最後に、tab311表と主キー索引及び、非ユニーク索引に対応したSparse Multidimensional Arrayの内容

1:      ^User.tab311D    =     2000000
2: ^User.tab311D(1) = $lb("",1,"0000000002","********************************* ...中略... ************************1",0)
3: ^User.tab311D(2) = $lb("",2,"0000000003","********************************* ...中略... ************************2",0)
4: ^User.tab311D(3) = $lb("",3,"0000000004","********************************* ...中略... ************************3",0)
...以下略...

1:      ^User.tab311I("tab311ix"," 0000000001",500000)    =     ""
2: ^User.tab311I("tab311ix"," 0000000001",1000000) = ""
3: ^User.tab311I("tab311ix"," 0000000001",1500000) = ""
4: ^User.tab311I("tab311ix"," 0000000001",2000000) = ""
5: ^User.tab311I("tab311ix"," 0000000002",1) = ""
6: ^User.tab311I("tab311ix"," 0000000002",500001) = ""
7: ^User.tab311I("tab311ix"," 0000000002",1000001) = ""
8: ^User.tab311I("tab311ix"," 0000000002",1500001) = ""
9: ^User.tab311I("tab311ix"," 0000000003",2) = ""
10: ^User.tab311I("tab311ix"," 0000000003",500002) = ""
11: ^User.tab311I("tab311ix"," 0000000003",1000002) = ""
12: ^User.tab311I("tab311ix"," 0000000003",1500002) = ""
13: ^User.tab311I("tab311ix"," 0000000004",3) = ""
14: ^User.tab311I("tab311ix"," 0000000004",500003) = ""
15: ^User.tab311I("tab311ix"," 0000000004",1000003) = ""
16: ^User.tab311I("tab311ix"," 0000000004",1500003) = ""
...以下略...


実行するクエリはOracle/MySQL/PostgreSQLで実行したものと同じです。

select
t1.unique_id,
t1.item_code,
(
select
max(t3.unique_id)
from
tab31 t2 join tab311 t3
on
t2.sub_item_code = t3.sub_item_code
and t3.is_delete = 0
where
t2.item_code = t1.item_code
and t2.is_delete = 0
) current_sub_item
from
tab3 t1
where
t1.unique_id between 1 and 10000
and t1.is_delete = 0
and t1.status_code = '00'


以下、管理ポータルのSQL実行プラン表示で取得した実行計画とコスト。
Index Only AccessではないのでCacheのSQLの世界で表アクセスを意味するオペレーションである、Read Master mapがスカラー問合せ部分に現れている。
ちなみに、索引アクセスを意味するオペレーションは、Read index mapのようだ。

実行プランが以下に表示されます:
クエリ文字列

SELECT t1 . unique_id , t1 . item_code , ( SELECT MAX ( t3 . unique_id ) FROM tab31 t2 JOIN tab311 t3 ON t2 . sub_item_code = t3 . sub_item_code AND t3 .
is_delete = ? WHERE t2 . item_code = t1 . item_code AND t2 . is_delete = ? ) current_sub_item FROM tab3 t1 WHERE t1 . unique_id BETWEEN ? AND ? AND t1 .
is_delete = ? AND t1 . status_code = ?

クエリプラン
相対コスト = 74082

Call module B, which populates bitmap temp-file A.
Read bitmap temp-file A, looping on ID.
For each row:
Read master map SQLUser.tab3.IDKEY, using the given idkey value.
Output the row.

module B
Read index map SQLUser.tab3.tab3_pk, looping on unique_id (with a range condition) and ID.
For each row:
Add ID bit to bitmap temp-file A.

subquery
Call module E.
Determine subquery result.

module E
Read index map SQLUser.tab31.tab31_pk, using the given %SQLUPPER(item_code), and looping on ID.
For each row:
Read master map SQLUser.tab31.IDKEY, using the given idkey value.
Read index map SQLUser.tab311.tab311_ix, using the given %SQLUPPER(sub_item_code), and looping on ID.
For each row:
Read master map SQLUser.tab311.IDKEY, using the given idkey value.
Accumulate the max(unique_id).


では、Index Only Accessによるチューニング。 Covering Indexを2つ作成します。(Oracle/MySQL/PostgreSQLで作成した索引と同じ名称にしてあります)

[oracle@pleco ˜]$ csession cache -U samples

ノード: pleco.macdeoracle.JP インスタンス: CACHE

SAMPLES>d $System.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------

The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
SAMPLES>>create index tab31_demo_ix on tab31(item_code, is_delete, sub_item_code)
1. create index tab31_demo_ix on tab31(item_code, is_delete, sub_item_code)

0 Rows Affected
statement prepare time: 4.8277s, elapsed execute time: 9.6483s.
---------------------------------------------------------------------------
SAMPLES>>create index tab311_demo_ix on tab311(sub_item_code, is_delete, unique_id)
2. create index tab311_demo_ix on tab311(sub_item_code, is_delete, unique_id)

0 Rows Affected
statement prepare time: 0.0130s, elapsed execute time: 8.6777s.
---------------------------------------------------------------------------
SAMPLES>>

索引は正しく作成されています。(管理ポータルから確認した結果)
Tab31_indexes_index_only

Tab311_indexes_inde_only

お〜〜〜〜〜っ!。 CacheのSQLワールドでもIndex Only Accessになってる〜〜〜。 (ヒントのような仕組みはないようなのですが、狙い通りの索引が利用されていますね)
スカラー副問合せの実行計画から Read Master Map(表に対応するSparse Multidimensional Array)をアクセスする操作が消え、Read index Map(索引に対応するSparse Multidimensional Array)をアクセスする操作だけになっていることが確認できた。 :)

以下、管理ポータルの「SQL実行」>「実行計画」で取得した実行計画

クエリプラン
相対コスト = 74082

Call module B, which populates bitmap temp-file A.
Read bitmap temp-file A, looping on ID.
For each row:
Read master map SQLUser.tab3.IDKEY, using the given idkey value.
Output the row.

module B
Read index map SQLUser.tab3.tab3_pk, looping on unique_id (with a range condition) and ID.
For each row:
Add ID bit to bitmap temp-file A.

subquery
Call module E.
Determine subquery result.

module E
Read index map SQLUser.tab31.tab31_demo_ix, using the given %SQLUPPER(item_code) and is_delete, and looping on %SQLUPPER(sub_item_code) and ID.
For each row:
Read index map SQLUser.tab311.tab311_demo_ix, using the given %SQLUPPER(sub_item_code) and is_delete, and looping on unique_id and ID.
For each row:
Accumulate the max(unique_id).

| | コメント (0) | トラックバック (0)

2012年8月21日 (火)

Index Only Accessネタのおまけ

2012/10/13追記
MySQL(InnoDB)の主キーはClustered Indexなので主キーアクセスである場合はCovering indexは不要ですね。(^^;; 
PostgreSQL9.2のIndex-only scanですが、vacuumさえしっかりやっていればcovering indexを利用するようになることを確認。

前述の2点を後日追加予定です。



随分間があいてしまいました、m(_ _)m

Oracle以外でも多数の商用/OSS RDBMSでIndex Only Accessできるんですよね。
ということで、MySQL/PostgreSQLの実行計画ではどのように表示されるのか、Oracleのように索引だけ作れば勝ってにIndex Only Accessやってくれるのか?、などなど簡単に確かめてみました。(備忘録)

OracleのIndex Only Accessは以下のエントリを参考にしてください。
いん!、イン!、Index どっぷり Inde Only Access生活w - Oracle OpenWorld Unconference presented by JPOUG
JPOUG SET EVENTS 20120721 - 「(続)いん!、イン!、Index 大人の事情縛りのSQLチューニング」資料公開

今回使ったのは、MySQL 5.5.27/PostgreSQL 9.1.4そしてBeta阪ですが9.2 Beta4です。
環境はOracleが乗ってる環境と同じです。

MacBook Air late 2010 13inch 2GB (MacOS X Lion)
VirtualBox4.1.18 for MacOS X
GuestOS:CentOS5.8 x86

ちなみに、データ作りに時間を割けなかったので、クラスタリングファクターはどちらのも低めとなっています。実行計画上どのように見えるのか知りたかっただけなので. (^^;;;

MySQL 5.5.27

Oracleのデモでも使っていたスカラー副問合せで試してみました。最初は、Index Rancge Scanをグルグル。
この場合は索引、表をそれぞれアクセスします。

mysql> explain 
-> select
-> t1.unique_id,
-> t1.item_code,
-> (
-> select
-> max(t3.unique_id)
-> from
-> tab31 t2 join tab311 t3
-> on
-> t2.sub_item_code = t3.sub_item_code
-> and t3.is_delete = 0
-> where
-> t2.item_code = t1.item_code
-> and t2.is_delete = 0
-> ) current_sub_item
-> from
-> tab3 t1
-> where
-> t1.unique_id between 1 and 10000
-> and t1.is_delete = 0
-> and t1.status_code = '00'
-> ;
+----+--------------------+-------+--------+---------------+-----------+---------+------------------------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+-------+--------+---------------+-----------+---------+------------------------+-------+-------------+
| 1 | PRIMARY | t1 | range | PRIMARY | PRIMARY | 5 | NULL | 20108 | Using where |
| 2 | DEPENDENT SUBQUERY | t2 | eq_ref | PRIMARY | PRIMARY | 45 | scott.t1.item_code | 1 | Using where |
| 2 | DEPENDENT SUBQUERY | t3 | ref | tab311_ix | tab311_ix | 31 | scott.t2.sub_item_code | 1 | Using where |
+----+--------------------+-------+--------+---------------+-----------+---------+------------------------+-------+-------------+
3 rows in set (0.00 sec)

mysql>


Index Only Accessさせた場合の実行計画です。
Extra列に Using indexと出ていればIndex Only Accessになっています。
ちなみに、Oracleとちがって勝ってにCovering Indexを使ってくれなかったのでSQLヒントを使っています。Oracleのヒントと随分書き方違うので戸惑うよ(^^;;;

mysql> explain
-> select
-> t1.unique_id,
-> t1.item_code,
-> (
-> select
-> max(t3.unique_id)
-> from
-> tab31 t2 ignore index(primary) join tab311 t3 ignore index(tab311_ix)
-> on
-> t2.sub_item_code = t3.sub_item_code
-> and t3.is_delete = 0
-> where
-> t2.item_code = t1.item_code
-> and t2.is_delete = 0
-> ) current_sub_item
-> from
-> tab3 t1
-> where
-> t1.unique_id between 1 and 10000
-> and t1.is_delete = 0
-> and t1.status_code = '00'
-> ;
+----+--------------------+-------+-------+----------------+----------------+---------+------------------------------+-------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+-------+-------+----------------+----------------+---------+------------------------------+-------+--------------------------+
| 1 | PRIMARY | t1 | range | PRIMARY | PRIMARY | 5 | NULL | 20108 | Using where |
| 2 | DEPENDENT SUBQUERY | t2 | ref | tab31_demo_ix | tab31_demo_ix | 47 | scott.t1.item_code,const | 10000 | Using where; Using index |
| 2 | DEPENDENT SUBQUERY | t3 | ref | tab311_demo_ix | tab311_demo_ix | 33 | scott.t2.sub_item_code,const | 10000 | Using where; Using index |
+----+--------------------+-------+-------+----------------+----------------+---------+------------------------------+-------+--------------------------+
3 rows in set (0.00 sec)

mysql>


PostgreSQL 9.1

PostgreSQL9.1まではIndex Only Accessが実装されていないとのこと。(実は今年になってはじめて知ったことなのですが、その時は、「え?! そうなの?」って感じでした)

※実行時間のバラツキがあるため実行統計情報から表や索引ブロックアクセス状況を確認しています。

scott=> select * from pg_statio_user_tables where relname in ('tab31','tab311');
relid | schemaname | relname | heap_blks_read | heap_blks_hit | idx_blks_read | idx_blks_hit | toast_blks_read | toast_blks_hit | tidx_blks_read | tidx_blks_hit
-------+------------+---------+----------------+---------------+---------------+--------------+-----------------+----------------+----------------+---------------
16401 | public | tab311 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
16395 | public | tab31 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0

scott=> select * from pg_statio_user_indexes where relname in ('tab31','tab311');
relid | indexrelid | schemaname | relname | indexrelname | idx_blks_read | idx_blks_hit
-------+------------+------------+---------+----------------+---------------+--------------
16401 | 16411 | public | tab311 | tab311_pk | 0 | 0
16395 | 16454 | public | tab31 | tab31_demo_ix | 0 | 0
16401 | 16455 | public | tab311 | tab311_demo_ix | 0 | 0

scott=> explain analyze verbose
scott-> select
scott-> t1.unique_id,
scott-> t1.item_code,
scott-> (
scott(> select
scott(> max(t3.unique_id)
scott(> from
scott(> tab31 t2 join tab311 t3
scott(> on
scott(> t2.sub_item_code = t3.sub_item_code
scott(> and t3.is_delete = 0
scott(> where
scott(> t2.item_code = t1.item_code
scott(> and t2.is_delete = 0
scott(> ) current_sub_item
scott-> from
scott-> tab3 t1
scott-> where
scott-> t1.unique_id between 1 and 10000
scott-> and t1.is_delete = 0
scott-> and t1.status_code = '00'
scott-> ;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using tab3_pk on public.tab3 t1 (cost=0.00..2050286.39 rows=10326 width=22) (actual time=45.613..6560.751 rows=10000 loops=1)
Output: t1.unique_id, t1.item_code, (SubPlan 1)
Index Cond: ((t1.unique_id >= 1::numeric) AND (t1.unique_id <= 10000::numeric))
Filter: ((t1.is_delete = 0::numeric) AND (t1.status_code = '00'::bpchar))
SubPlan 1
-> Aggregate (cost=198.47..198.48 rows=1 width=6) (actual time=0.650..0.651 rows=1 loops=10000)
Output: max(t3.unique_id)
-> Nested Loop (cost=0.00..198.46 rows=4 width=6) (actual time=0.143..0.621 rows=40 loops=10000)
Output: t3.unique_id
-> Index Scan using tab31_pk on public.tab31 t2 (cost=0.00..8.58 rows=1 width=11) (actual time=0.010..0.011 rows=1 loops=10000)
Output: t2.item_code, t2.sub_item_code, t2.data, t2.is_delete
Index Cond: (t2.item_code = t1.item_code)
Filter: (t2.is_delete = 0::numeric)
-> Index Scan using tab311_ix on public.tab311 t3 (cost=0.00..189.27 rows=49 width=17) (actual time=0.130..0.565 rows=40 loops=10000)
Output: t3.unique_id, t3.sub_item_code, t3.data, t3.is_delete
Index Cond: (t3.sub_item_code = t2.sub_item_code)
Filter: (t3.is_delete = 0::numeric)
Total runtime: 6566.636 ms
(18 行)

時間: 6618.795 ms
scott=>

適切な索引を利用しているのでIndex Unique/Range Scanとテーブルブロックへのアクセスが確認できます。(当然といえば当然ですよね)

scott=> select * from pg_statio_user_tables where relname in ('tab31','tab311');
relid | schemaname | relname | heap_blks_read | heap_blks_hit | idx_blks_read | idx_blks_hit | toast_blks_read | toast_blks_hit | tidx_blks_read | tidx_blks_hit
-------+------------+---------+----------------+---------------+---------------+--------------+-----------------+----------------+----------------+---------------
16401 | public | tab311 | 15389 | 36920 | 1983 | 30033 | 0 | 0 | 0 | 0
16395 | public | tab31 | 401 | 9599 | 64 | 30057 | 0 | 0 | 0 | 0

scott=> select * from pg_statio_user_indexes where relname in ('tab31','tab311');
relid | indexrelid | schemaname | relname | indexrelname | idx_blks_read | idx_blks_hit
-------+------------+------------+---------+----------------+---------------+--------------
16401 | 16411 | public | tab311 | tab311_pk | 0 | 0
16395 | 16454 | public | tab31 | tab31_demo_ix | 64 | 30057
16401 | 16455 | public | tab311 | tab311_demo_ix | 1983 | 30033

Index Only AccessさせるためのCovering Index(FAT index)を作ってみましたが、Index Range/Unique Scanのままですね。
ちなみに、PostgreSQLってOracleやMySQLのようなSQLヒントがありません。(あったらごめんなさい。調べきれてないだけです。m(_ _)m
Covering Indexだけを残して邪魔な索引を削除することでCovering indexをアクセスさせています。

scott=> select * from pg_statio_user_tables where relname in ('tab31','tab311');
relid | schemaname | relname | heap_blks_read | heap_blks_hit | idx_blks_read | idx_blks_hit | toast_blks_read | toast_blks_hit | tidx_blks_read | tidx_blks_hit
-------+------------+---------+----------------+---------------+---------------+--------------+-----------------+----------------+----------------+---------------
16401 | public | tab311 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
16395 | public | tab31 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0

scott=> select * from pg_statio_user_indexes where relname in ('tab31','tab311');
relid | indexrelid | schemaname | relname | indexrelname | idx_blks_read | idx_blks_hit
-------+------------+------------+---------+----------------+---------------+--------------
16401 | 16411 | public | tab311 | tab311_pk | 0 | 0
16395 | 16454 | public | tab31 | tab31_demo_ix | 0 | 0
16401 | 16455 | public | tab311 | tab311_demo_ix | 0 | 0


scott=> explain analyze verbose
scott-> select
scott-> t1.unique_id,
scott-> t1.item_code,
scott-> (
scott(> select
scott(> max(t3.unique_id)
scott(> from
scott(> tab31 t2 join tab311 t3
scott(> on
scott(> t2.sub_item_code = t3.sub_item_code
scott(> and t3.is_delete = 0
scott(> where
scott(> t2.item_code = t1.item_code
scott(> and t2.is_delete = 0
scott(> ) current_sub_item
scott-> from
scott-> tab3 t1
scott-> where
scott-> t1.unique_id between 1 and 10000
scott-> and t1.is_delete = 0
scott-> and t1.status_code = '00'
scott-> ;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using tab3_pk on public.tab3 t1 (cost=0.00..2109597.62 rows=10326 width=22) (actual time=0.677..1808.056 rows=10000 loops=1)
Output: t1.unique_id, t1.item_code, (SubPlan 1)
Index Cond: ((t1.unique_id >= 1::numeric) AND (t1.unique_id <= 10000::numeric))
Filter: ((t1.is_delete = 0::numeric) AND (t1.status_code = '00'::bpchar))
SubPlan 1
-> Aggregate (cost=204.21..204.22 rows=1 width=6) (actual time=0.176..0.176 rows=1 loops=10000)
Output: max(t3.unique_id)
-> Nested Loop (cost=0.00..204.20 rows=4 width=6) (actual time=0.024..0.137 rows=40 loops=10000)
Output: t3.unique_id
-> Index Scan using tab31_demo_ix on public.tab31 t2 (cost=0.00..8.76 rows=1 width=11) (actual time=0.007..0.009 rows=1 loops=10000)
Output: t2.item_code, t2.sub_item_code, t2.data, t2.is_delete
Index Cond: ((t2.item_code = t1.item_code) AND (t2.is_delete = 0::numeric))
-> Index Scan using tab311_demo_ix on public.tab311 t3 (cost=0.00..194.84 rows=49 width=17) (actual time=0.013..0.068 rows=40 loops=10000)
Output: t3.unique_id, t3.sub_item_code, t3.data, t3.is_delete
Index Cond: ((t3.sub_item_code = t2.sub_item_code) AND (t3.is_delete = 0::numeric))
Total runtime: 4762.819 ms
(16 行)

時間: 4855.100 ms
scott=>

実行計画上、Filterはなくなりましたが、表ブロックもアクセスしているのでIndex Only Accessにはなっていません。(ほんとにIndex Only AccessというかIndex-only Scanは9.1までは実装されていないんですね。)

scott=> select * from pg_statio_user_tables where relname in ('tab31','tab311');
relid | schemaname | relname | heap_blks_read | heap_blks_hit | idx_blks_read | idx_blks_hit | toast_blks_read | toast_blks_hit | tidx_blks_read | tidx_blks_hit
-------+------------+---------+----------------+---------------+---------------+--------------+-----------------+----------------+----------------+---------------
16401 | public | tab311 | 15390 | 36919 | 1982 | 30034 | 0 | 0 | 0 | 0
16395 | public | tab31 | 401 | 9599 | 62 | 30059 | 0 | 0 | 0 | 0

scott=> select * from pg_statio_user_indexes where relname in ('tab31','tab311');
relid | indexrelid | schemaname | relname | indexrelname | idx_blks_read | idx_blks_hit
-------+------------+------------+---------+----------------+---------------+--------------
16401 | 16411 | public | tab311 | tab311_pk | 0 | 0
16395 | 16454 | public | tab31 | tab31_demo_ix | 62 | 30059
16401 | 16455 | public | tab311 | tab311_demo_ix | 1982 | 30034

PostgreSQL 9.2 Beta4

最後に、PostgreSQL 9.2 Beta4 です。このリリースではPostgreSQLでは初めて、Index Only Access(マニュアルでは Index-only Scanと記載されています)
PostgreSQL方面の方がIndex-only Scanと書くかたが多いのもこの影響でしょうね。日本人からするとIndex Only AccessよりIndex Only Scanの方が発音しやすい?(私だけか?)気がしますw

scott=> explain analyze verbose
scott-> select
scott-> t1.unique_id,
scott-> t1.item_code,
scott-> (
scott(> select
scott(> max(t3.unique_id)
scott(> from
scott(> tab31 t2 join tab311 t3
scott(> on
scott(> t2.sub_item_code = t3.sub_item_code
scott(> and t3.is_delete = 0
scott(> where
scott(> t2.item_code = t1.item_code
scott(> and t2.is_delete = 0
scott(> ) current_sub_item
scott-> from
scott-> tab3 t1
scott-> where
scott-> t1.unique_id between 1 and 10000
scott-> and t1.is_delete = 0
scott-> and t1.status_code = '00'
scott-> ;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using tab3_pk on public.tab3 t1 (cost=0.00..1925706.85 rows=9421 width=22) (actual time=41.205..6202.096 rows=10000 loops=1)
Output: t1.unique_id, t1.item_code, (SubPlan 1)
Index Cond: ((t1.unique_id >= 1::numeric) AND (t1.unique_id <= 10000::numeric))
Filter: ((t1.is_delete = 0::numeric) AND (t1.status_code = '00'::bpchar))
SubPlan 1
-> Aggregate (cost=204.32..204.33 rows=1 width=6) (actual time=0.613..0.614 rows=1 loops=10000)
Output: max(t3.unique_id)
-> Nested Loop (cost=0.00..204.31 rows=4 width=6) (actual time=0.110..0.584 rows=40 loops=10000)
Output: t3.unique_id
-> Index Scan using tab31_pk on public.tab31 t2 (cost=0.00..11.35 rows=1 width=11) (actual time=0.012..0.013 rows=1 loops=10000)
Output: t2.item_code, t2.sub_item_code, t2.data, t2.is_delete
Index Cond: (t2.item_code = t1.item_code)
Filter: (t2.is_delete = 0::numeric)
-> Index Scan using tab311_ix on public.tab311 t3 (cost=0.00..192.47 rows=49 width=17) (actual time=0.095..0.525 rows=40 loops=10000)
Output: t3.unique_id, t3.sub_item_code, t3.data, t3.is_delete
Index Cond: (t3.sub_item_code = t2.sub_item_code)
Filter: (t3.is_delete = 0::numeric)
Total runtime: 6207.935 ms
(18 行)

時間: 6313.789 ms
scott=>

9.2でもIndex Only Accessでなければ表ブロックもアクセスしますよね〜。そりゃそうだ。:)

scott=> select * from pg_statio_user_tables where relname in ('tab31','tab311');
relid | schemaname | relname | heap_blks_read | heap_blks_hit | idx_blks_read | idx_blks_hit | toast_blks_read | toast_blks_hit | tidx_blks_read | tidx_blks_hit
-------+------------+---------+----------------+---------------+---------------+--------------+-----------------+----------------+----------------+---------------
16404 | public | tab311 | 15390 | 36919 | 1649 | 29996 | 0 | 0 | 0 | 0
16398 | public | tab31 | 401 | 9599 | 39 | 30037 | 0 | 0 | 0 | 0

scott=> select * from pg_statio_user_indexes where relname in ('tab31','tab311');
relid | indexrelid | schemaname | relname | indexrelname | idx_blks_read | idx_blks_hit
-------+------------+------------+---------+--------------+---------------+--------------
16404 | 16414 | public | tab311 | tab311_pk | 0 | 0
16398 | 16432 | public | tab31 | tab31_pk | 39 | 30037
16404 | 16434 | public | tab311 | tab311_ix | 1649 | 29996

いよいよ、PostgreSQL9.2 Beta4のIndex-only Scanの番です。:)
お〜〜〜っ、 Index Scan using xxxxという部分が、Index Only Scan using xxxxとなっています! が、 Heap Fetches 400000とある? どゆこと?

scott=> explain analyze verbose
scott-> select
scott-> t1.unique_id,
scott-> t1.item_code,
scott-> (
scott(> select
scott(> max(t3.unique_id)
scott(> from
scott(> tab31 t2 join tab311 t3
scott(> on
scott(> t2.sub_item_code = t3.sub_item_code
scott(> and t3.is_delete = 0
scott(> where
scott(> t2.item_code = t1.item_code
scott(> and t2.is_delete = 0
scott(> ) current_sub_item
scott-> from
scott-> tab3 t1
scott-> where
scott-> t1.unique_id between 1 and 10000
scott-> and t1.is_delete = 0
scott-> and t1.status_code = '00'
scott-> ;

QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using tab3_pk on public.tab3 t1 (cost=0.00..2000896.05 rows=9421 width=22) (actual time=0.410..1728.699 rows=10000 loops=1)
Output: t1.unique_id, t1.item_code, (SubPlan 1)
Index Cond: ((t1.unique_id >= 1::numeric) AND (t1.unique_id <= 10000::numeric))
Filter: ((t1.is_delete = 0::numeric) AND (t1.status_code = '00'::bpchar))
SubPlan 1
-> Aggregate (cost=212.30..212.31 rows=1 width=6) (actual time=0.168..0.168 rows=1 loops=10000)
Output: max(t3.unique_id)
-> Nested Loop (cost=0.00..212.29 rows=4 width=6) (actual time=0.023..0.131 rows=40 loops=10000)
Output: t3.unique_id
-> Index Only Scan using tab31_demo_ix on public.tab31 t2 (cost=0.00..13.13 rows=1 width=11) (actual time=0.006..0.007 rows=1 loops=10000)
Output: t2.item_code, t2.is_delete, t2.sub_item_code
Index Cond: ((t2.item_code = t1.item_code) AND (t2.is_delete = 0::numeric))
Heap Fetches: 10000
-> Index Only Scan using tab311_demo_ix on public.tab311 t3 (cost=0.00..198.67 rows=49 width=17) (actual time=0.013..0.066 rows=40 loops=10000)
Output: t3.sub_item_code, t3.is_delete, t3.unique_id
Index Cond: ((t3.sub_item_code = t2.sub_item_code) AND (t3.is_delete = 0::numeric))
Heap Fetches: 400000
Total runtime: 5412.612 ms
(18 行)

時間: 5465.400 ms
scott=>

やはり! Heap Fetchesとあるのでおかしいと思っていたら...orz. なんで表ブロックアクセスしてんの〜〜〜っ。実行計画は、Index Only Scan。謎。

scott=> select * from pg_statio_user_tables where relname in ('tab31','tab311');
relid | schemaname | relname | heap_blks_read | heap_blks_hit | idx_blks_read | idx_blks_hit | toast_blks_read | toast_blks_hit | tidx_blks_read | tidx_blks_hit
-------+------------+---------+----------------+---------------+---------------+--------------+-----------------+----------------+----------------+---------------
16404 | public | tab311 | 15389 | 36920 | 1983 | 30033 | 0 | 0 | 0 | 0
16398 | public | tab31 | 401 | 9599 | 64 | 30057 | 0 | 0 | 0 | 0

scott=> select * from pg_statio_user_indexes where relname in ('tab31','tab311');
relid | indexrelid | schemaname | relname | indexrelname | idx_blks_read | idx_blks_hit
-------+------------+------------+---------+----------------+---------------+--------------
16404 | 16414 | public | tab311 | tab311_pk | 0 | 0
16398 | 16435 | public | tab31 | tab31_demo_ix | 64 | 30057
16404 | 16436 | public | tab311 | tab311_demo_ix | 1983 | 30033


何故、Inde Only Scanなのに表ブロックをアクセスしてしまうのか、わかった〜〜〜っ、と思う。(vacuum analyze が必要らしい。ちなみに前述の結果はデータをINSERTし、analyzeコマンドだけを実施した状態だった)

気持ちを落ち着けて〜〜〜!

[oracle@leaffish ˜]$ psql -U oracle scott
タイミングは on です。
psql (9.2beta4)
"help" でヘルプを表示します.

scott=# vacuum analyze tab311;
scott=# vacuum analyze tab31;


こんどこそ! できた〜〜〜〜っ!と思う。
Heap Fetchesも0だし、表ブロックへのアクセスもほぼない。ほぼない。大切なので2度書きました。表ブロックへのアクセスは0にはならないようです。今のところ。こまけーことはいいんだよ的で、ワイルド。 :) 数ブロックアクセスしているのはどのようなブロックなのでしょうね

scott=> explain analyze verbose
scott-> select
scott-> t1.unique_id,
scott-> t1.item_code,
scott-> (
scott(> select
scott(> max(t3.unique_id)
scott(> from
scott(> tab31 t2 join tab311 t3
scott(> on
scott(> t2.sub_item_code = t3.sub_item_code
scott(> and t3.is_delete = 0
scott(> where
scott(> t2.item_code = t1.item_code
scott(> and t2.is_delete = 0
scott(> ) current_sub_item
scott-> from
scott-> tab3 t1
scott-> where
scott-> t1.unique_id between 1 and 10000
scott-> and t1.is_delete = 0
scott-> and t1.status_code = '00'
scott-> ;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using tab3_pk on public.tab3 t1 (cost=0.00..188801.90 rows=9921 width=22) (actual time=0.255..1116.525 rows=10000 loops=1)
Output: t1.unique_id, t1.item_code, (SubPlan 1)
Index Cond: ((t1.unique_id >= 1::numeric) AND (t1.unique_id <= 10000::numeric))
Filter: ((t1.is_delete = 0::numeric) AND (t1.status_code = '00'::bpchar))
SubPlan 1
-> Aggregate (cost=18.95..18.96 rows=1 width=6) (actual time=0.108..0.109 rows=1 loops=10000)
Output: max(t3.unique_id)
-> Nested Loop (cost=0.00..18.94 rows=4 width=6) (actual time=0.019..0.081 rows=40 loops=10000)
Output: t3.unique_id
-> Index Only Scan using tab31_demo_ix on public.tab31 t2 (cost=0.00..9.13 rows=1 width=11) (actual time=0.005..0.005 rows=1 loops=10000)
Output: t2.item_code, t2.is_delete, t2.sub_item_code
Index Cond: ((t2.item_code = t1.item_code) AND (t2.is_delete = 0::numeric))
Heap Fetches: 0
-> Index Only Scan using tab311_demo_ix on public.tab311 t3 (cost=0.00..9.32 rows=49 width=17) (actual time=0.012..0.036 rows=40 loops=10000)
Output: t3.sub_item_code, t3.is_delete, t3.unique_id
Index Cond: ((t3.sub_item_code = t2.sub_item_code) AND (t3.is_delete = 0::numeric))
Heap Fetches: 0
Total runtime: 1121.140 ms
(18 行)

時間: 1123.194 ms

scott=> select * from pg_statio_user_tables where relname in ('tab31','tab311');
relid | schemaname | relname | heap_blks_read | heap_blks_hit | idx_blks_read | idx_blks_hit | toast_blks_read | toast_blks_hit | tidx_blks_read | tidx_blks_hit
-------+------------+---------+----------------+---------------+---------------+--------------+-----------------+----------------+----------------+---------------
16404 | public | tab311 | 0 | 3 | 0 | 32015 | 0 | 0 | 0 | 0
16398 | public | tab31 | 0 | 1 | 0 | 30120 | 0 | 0 | 0 | 0

scott=> select * from pg_statio_user_indexes where relname in ('tab31','tab311');
relid | indexrelid | schemaname | relname | indexrelname | idx_blks_read | idx_blks_hit
-------+------------+------------+---------+----------------+---------------+--------------
16404 | 16414 | public | tab311 | tab311_pk | 0 | 0
16398 | 16421 | public | tab31 | tab31_demo_ix | 0 | 30120
16404 | 16422 | public | tab311 | tab311_demo_ix | 0 | 32015




参考:
INDEX FULL SCANを狙う - MySQL Casual Advent Calendar 2011
Covering Index と self-join と MySQL
How are index-only scans implemented in InnoDB
PostgreSQL 9.2 highlight: Index-only scans
PostgreSQLアーキテクチャ入門(INSIGHT OUT 2011)

| | コメント (0) | トラックバック (0)

2012年5月 6日 (日)

オプティマイザをだましちゃお! の続き。

オプティマイザをだましちゃお! の続きです。

リテラル値指定、かつ、全件ヒットするからTABLE FULL SCANになるはずなのに、何故、INDEX RANGE SCANしたのか? の種明かしです


status列の値が全て 0 の状態で統計情報を取得した状態で、status列全てを 2 に更新、その後統計情報は再取得していません。
オプティマイザは、status列が全て 0 だと思い込んでいるわけですね、実際は、全て 2 なのに。

08:55:05 SCOTT> update deluding_tab set status = 2;

100000行が更新されました。

08:55:15 SCOTT> commit;

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

status列が全て、またはほぼ、 0 なのであれば、全データをINDEX RANGE SCANで取得するなんて通常はあり得ないですよね :) 

列値と統計情報の取得タイミングでオプティマイザを騙しているわけです。
(これは意図的ですが、意図せずオプティマイザが誤った実行計画を算出して夜中に電話が鳴った,なんて方は意外に多いかも…w)


念のため 10053 トレースで status = 1 と status = 2 で同じ実行計画を求めているかも確認してみると…

08:55:18 SCOTT> alter session set events '10053 trace name context forever, level 1';

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

08:55:24 SCOTT> select * from deluding_tab where status = 1;

08:55:37 SCOTT> select * from deluding_tab where status = 2;


リテラル値部分が異なる事以外は皆同じということが分かります。うまくオプティマイザを騙せてますね :)

discus$ diff status_1.txt status_2.txt
1c1
< *** 2012-05-06 08:55:37.663
---
> *** 2012-05-06 08:55:50.630
23,24c23,24
< ----- Current SQL Statement for this session (sql_id=2pa417mwk4crp) -----
< select * from deluding_tab where status = 1
---
> ----- Current SQL Statement for this session (sql_id=7sb6ypqd43bdr) -----
> select * from deluding_tab where status = 2
824c824
< CBQT: Validity checks failed for 2pa417mwk4crp.
---
> CBQT: Validity checks failed for 7sb6ypqd43bdr.
847c847
< CBQT: Validity checks failed for 2pa417mwk4crp.
---
> CBQT: Validity checks failed for 7sb6ypqd43bdr.
870c870
< "DELUDING_TAB"."STATUS"=1
---
> "DELUDING_TAB"."STATUS"=2
872c872
< finally: "DELUDING_TAB"."STATUS"=1
---
> finally: "DELUDING_TAB"."STATUS"=2
874c874
< apadrv-start sqlid=3073848844558152437
---
> apadrv-start sqlid=8947771396877036983
883c883
< SELECT "DELUDING_TAB"."ID" "ID","DELUDING_TAB"."STATUS" "STATUS","DELUDING_TAB"."DATA" "DATA" FROM "SCOTT"."DELUDING_TAB"
"DELUDING_TAB" WHERE "DELUDING_TAB"."STATUS"=1
---
> SELECT "DELUDING_TAB"."ID" "ID","DELUDING_TAB"."STATUS" "STATUS","DELUDING_TAB"."DATA" "DATA" FROM "SCOTT"."DELUDING_TAB"
"DELUDING_TAB" WHERE "DELUDING_TAB"."STATUS"=2
893c893
< select * from deluding_tab where status = 1
---
> select * from deluding_tab where status = 2
959,960c959,960
< id=0 frofkks[i] (index start key) predicate="DELUDING_TAB"."STATUS"=1
< id=0 frofkke[i] (index stop key) predicate="DELUDING_TAB"."STATUS"=1
---
> id=0 frofkks[i] (index start key) predicate="DELUDING_TAB"."STATUS"=2
> id=0 frofkke[i] (index stop key) predicate="DELUDING_TAB"."STATUS"=2
980,982c980,982
< sql_id=2pa417mwk4crp plan_hash_value=1226994206 problem_type=3
< ----- Current SQL Statement for this session (sql_id=2pa417mwk4crp) -----
< select * from deluding_tab where status = 1
---
> sql_id=7sb6ypqd43bdr plan_hash_value=1226994206 problem_type=3
> ----- Current SQL Statement for this session (sql_id=7sb6ypqd43bdr) -----
> select * from deluding_tab where status = 2
984c984
< sql=select * from deluding_tab where status = 1
---
> sql=select * from deluding_tab where status = 2
1000c1000
< 2 - access("STATUS"=1)
---
> 2 - access("STATUS"=2)
discus$


status列の値が全て 2 なのですが、統計情報は 0 で取得されているので status = 0 で検索すると、索引は使わず TABLE FULL SCANですよね。対象データは 0 件なんですけど。 (^^;;;

14:52:04 SCOTT> select * from deluding_tab where status = 0;

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

経過: 00:00:00.64

実行計画
----------------------------------------------------------
Plan hash value: 1998905050

----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 99991 | 48M| 1947 (1)| 00:00:24 |
|* 1 | TABLE ACCESS FULL| DELUDING_TAB | 99991 | 48M| 1947 (1)| 00:00:24 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("STATUS"=0)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
7184 consistent gets
7174 physical reads
0 redo size
487 bytes sent via SQL*Net to client
509 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed




ここまでのあらすじ

オプティマイザをだましちゃお! (マジック・ザ・ギャザリング風w かも)

| | コメント (0) | トラックバック (0)

2012年5月 1日 (火)

オプティマイザをだましちゃお! (マジック・ザ・ギャザリング風w かも)

ということで、準備から。なにやら索引がいるのか怪しい列にまでBツリー索引を作成しちゃってます ;)

23:34:03 SYS> conn scott/tiger
接続されました。
23:36:01 SCOTT>
23:36:01 SCOTT>
23:36:01 SCOTT> create table deluding_tab (id number not null, status number(2) not null, data varchar2(500)) nologging;

表が作成されました。

23:50:47 SCOTT> begin for i in 1..100000 loop insert into deluding_tab values(i,0,lpad(i,500,'*')); end loop; end;
23:55:22 2 /

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

23:55:27 SCOTT> commit;

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

23:55:31 SCOTT> alter table deluding_tab add constraint pk_deluding_tab primary key (id) using index nologging;

表が変更されました。

23:56:07 SCOTT> create index ix_deluding_tab on deluding_tab(status) nologging;

索引が作成されました。

23:58:26 SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'DELUDING_TAB', -
23:58:34 > no_invalidate=>false,cascade=>true,method_opt=>'FOR ALL COLUMNS SIZE AUTO');

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

ヒストグラムの状態も見ておきましょ。

TABLE_NAME                     COLUMN_NAME                    NUM_BUCKETS HISTOGRAM
------------------------------ ------------------------------ ----------- ---------------
DELUDING_TAB ID 1 NONE
DELUDING_TAB STATUS 1 NONE
DELUDING_TAB DATA 1 NONE

登録したデータ件数も見ておきましょうか。

00:03:27 SCOTT> 
00:08:41 SCOTT> select count(1) from deluding_tab;

COUNT(1)
----------
100000

では、オプティマイザとデュエル!


アンタップ、アップキープ、ドロー! 

00:03:54 SCOTT> select * from deluding_tab where status = 1;

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

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 1226994206

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU) | Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 508 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| DELUDING_TAB | 1 | 508 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IX_DELUDING_TAB | 1 | | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("STATUS"=1)


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
2 consistent gets
0 physical reads
0 redo size
487 bytes sent via SQL*Net to client
509 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed

データはヒットしていないですね。索引レンジスキャンは妥当な実行計画:)

お〜〜っと、ここで、インスタント! 全データをヒント無しで、索引レンジスキャン!

ちなみに、遊戯王ばかりで、マジック・ザ・ギャザリングをしらない、最近の子の為に解説しておくと、フラッシュタイミングで使うカードと似たような使い方をするカードのことだぉ


00:07:27 SCOTT> select * from deluding_tab where status = 2;

100000行が選択されました。

経過: 00:00:02.32

実行計画
----------------------------------------------------------
Plan hash value: 1226994206

------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU) | Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 508 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| DELUDING_TAB | 1 | 508 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IX_DELUDING_TAB | 1 | | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("STATUS"=2)


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
20166 consistent gets
0 physical reads
0 redo size
53229491 bytes sent via SQL*Net to client
73846 bytes received via SQL*Net from client
6668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
100000 rows processed


ターンエンド。

リテラル値指定、かつ、全件ヒットするからTABLE FULL SCANになるはずなのに、何故、INDEX RANGE SCANしてすげ〜時間かかっているんだ〜〜〜〜というマジックの秘密は次回。

バインド変数使ってなくてもオプティマイザをだませるんだぉ

つづく。

| | コメント (0) | トラックバック (0)

2012年4月28日 (土)

述語のつづきの続き。

前回の続きです。


今回の例も今ひとつといえばそうなのだけど、述語プッシュで索引が利用されることを期待した書き方なのに残念な結果になっている以下のようなパターン、意外と見かけるんですよー
オプティマイザが進化して今以上に賢くなったら気にしなくても最適化してくれるかもしれないけど………

11:28:59 SCOTT> l   
1 select
2 v1.id
3 ,v1.data
4 from
5 (
6 select
7 to_char(id) as id
8 ,data
9 from
10 push_pred_test1
11 union
12 select
13 to_char(id) as id
14 ,data
15 from
16 push_pred_test2
17 ) v1
18 where
19* v1.id = 10
11:30:40 SCOTT> /

11行が選択されました。

経過: 00:00:01.58

実行計画
----------------------------------------------------------
Plan hash value: 2712236156

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU) | Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 11 | 814 | 4857 (1)| 00:00:59 |
| 1 | VIEW | | 11 | 814 | 4857 (1)| 00:00:59 |
| 2 | SORT UNIQUE | | 11 | 1166 | 4857 (91)| 00:00:59 |
| 3 | UNION-ALL | | | | | |
|* 4 | TABLE ACCESS FULL| PUSH_PRED_TEST1 | 1 | 106 | 445 (1)| 00:00:06 |
|* 5 | TABLE ACCESS FULL| PUSH_PRED_TEST2 | 10 | 1060 | 4410 (1)| 00:00:53 |
------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - filter(TO_NUMBER(TO_CHAR("ID"))=10)
5 - filter(TO_NUMBER(TO_CHAR("ID"))=10)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
17554 consistent gets
15960 physical reads
0 redo size
1819 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
11 rows processed

11:30:43 SCOTT>


こんな実行計画を期待して書いたはず (^^)

11:31:49 SCOTT> l
1 select
2 to_char(v1.id) as id
3 ,v1.data
4 from
5 (
6 select
7 id
8 ,data
9 from
10 push_pred_test1
11 union
12 select
13 id
14 ,data
15 from
16 push_pred_test2
17 ) v1
18 where
19* v1.id = 10
11:31:50 SCOTT> /

11行が選択されました。

経過: 00:00:00.00

実行計画
----------------------------------------------------------
Plan hash value: 2774814350

--------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU) | Time |
--------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 11 | 715 | 8 (25)| 00:00:01 |
| 1 | VIEW | | 11 | 715 | 8 (25)| 00:00:01 |
| 2 | SORT UNIQUE | | 11 | 1166 | 8 (75)| 00:00:01 |
| 3 | UNION-ALL | | | | | |
| 4 | TABLE ACCESS BY INDEX ROWID| PUSH_PRED_TEST1 | 1 | 106 | 2 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | PK_PUSH_PRED_TEST1 | 1 | | 1 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID| PUSH_PRED_TEST2 | 10 | 1060 | 4 (0)| 00:00:01 |
|* 7 | INDEX RANGE SCAN | PK_PUSH_PRED_TEST2 | 10 | | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

5 - access("ID"=10)
7 - access("ID"=10)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
7 consistent gets
0 physical reads
0 redo size
1819 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
11 rows processed

11:31:51 SCOTT>




ここまでのあらすじ

述語 ;)
述語 ;) のつづき

| | コメント (0) | トラックバック (0)

2012年4月27日 (金)

述語 ;) のつづき

さて、前回のつづきです。

以下のようにtable full scanになっちゃってるのをもうちょっといい感じにチューニングするには? ってとこまででした。

わかっちゃったもんね〜〜〜〜〜〜の方〜〜〜〜〜〜〜っ。 沢山いるようですね。頼もしいです:)

赤字で示した部分が重要なんですよーーーー。TO_CHAR()関数が使われていて述語プッシュでうまく索引が利用できないのが原因なんですよ。

22:52:28 SCOTT> set autot trace exp stat
22:52:34 SCOTT>
22:52:35 SCOTT>
22:52:35 SCOTT> l
1 select
2 t1.id
3 ,t1.data
4 ,t1.ctr
5 from
6 (
7 select
8 to_char(t0.id) as id
9 ,data
10 ,count(1) as ctr
11 from
12 push_pred_tab1 t0
13 group by
14 t0.id
15 ,t0.data
16 ) t1
17 where
18 t1.id between 1 and 20
19 order by
20* t1.id
22:52:38 SCOTT> /

20行が選択されました。

経過: 00:00:00.07

実行計画
----------------------------------------------------------
Plan hash value: 62726154

--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU) | Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 250 | 123K| 1950 (1)| 00:00:24 |
| 1 | SORT ORDER BY | | 250 | 123K| 1950 (1)| 00:00:24 |
| 2 | HASH GROUP BY | | 250 | 123K| 1950 (1)| 00:00:24 |
|* 3 | TABLE ACCESS FULL| PUSH_PRED_TAB1 | 250 | 123K| 1948 (1)| 00:00:24 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - filter(TO_NUMBER(TO_CHAR("T0"."ID"))>=1 AND
TO_NUMBER(TO_CHAR("T0"."ID"))<=20)


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
7184 consistent gets
0 physical reads
0 redo size
11132 bytes sent via SQL*Net to client
530 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
20 rows processed

改善後、述語が副問合せにプッシュされて索引レンジスキャンになってます :) めでたしめでたしなんですが 例があれなんで、次回はもう少し本物っぽいのでやってみますね。

 22:54:14 SCOTT> 
22:54:15 SCOTT> l
1 select
2 to_char(t1.id) as id
3 ,t1.data
4 ,t1.ctr
5 from
6 (
7 select
8 t0.id
9 ,data
10 ,count(1) as ctr
11 from
12 push_pred_tab1 t0
13 group by
14 t0.id
15 ,t0.data
16 ) t1
17 where
18 t1.id between 1 and 20
19 order by
20* t1.id
22:54:15 SCOTT> /

20行が選択されました。

経過: 00:00:00.00

実行計画
----------------------------------------------------------
Plan hash value: 1209889820

--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20 | 10120 | 5 (20)| 00:00:01 |
| 1 | SORT GROUP BY | | 20 | 10120 | 5 (20)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| PUSH_PRED_TAB1 | 20 | 10120 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | PK_PUSH_PRED_TAB1 | 20 | | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("T0"."ID">=1 AND "T0"."ID"<=20)



統計
----------------------------------------------------------
1 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
11132 bytes sent via SQL*Net to client
530 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
20 rows processed

22:54:17 SCOTT>

ということで次回へつづく。


ここまでのあらすじ

述語 ;)

| | コメント (0) | トラックバック (0)

2012年4月25日 (水)

述語 ;)

書こうと思っているうちに時間がなくて書けなかったので、簡単なチューニングネタを一つ。


こんな感じのクエリ、どうやったら、いい感じにチューニングできるんでしょうね?

ちなみに、index only accessにする必要はないです。 やってもいいけどw

(いろいろ端折りすぎたらお題のクエリイマイチw ま、いいっか)

・準備 - 以下のような表を作って、データを登録しておきます。

22:33:20 SCOTT> create table push_pred_tab1 (id number not null, data varchar2(500)) nologging;

表が作成されました。

経過: 00:00:00.02
22:33:53 SCOTT> begin for i in 1..100000 loop insert into push_pred_tab1 values(i,lpad(i,500,'*')); end loop; commit; end;
22:36:30 2 /

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

経過: 00:00:04.17
22:38:03 SCOTT> alter table push_pred_tab1 add constraint pk_push_pred_tab1 primary key(id) using index nologging;

表が変更されました。

経過: 00:00:00.18
22:38:28 SCOTT>
22:47:55 SCOTT> exec
dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'PUSH_PRED_TAB1',no_invalidate=>false,cascade=>true);

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

経過: 00:00:00.31

SQL文と実行計画は以下の通りで、以下のSQL文、どうやったらいい感じにチューニングできるでしょうか?

22:52:28 SCOTT> set autot trace exp stat
22:52:34 SCOTT>
22:52:35 SCOTT>
22:52:35 SCOTT> l
1 select
2 t1.id
3 ,t1.data
4 ,t1.ctr
5 from
6 (
7 select
8 to_char(t0.id) as id
9 ,data
10 ,count(1) as ctr
11 from
12 push_pred_tab1 t0
13 group by
14 t0.id
15 ,t0.data
16 ) t1
17 where
18 t1.id between 1 and 20
19 order by
20* t1.id
22:52:38 SCOTT> /

20行が選択されました。

経過: 00:00:00.07

実行計画
----------------------------------------------------------
Plan hash value: 62726154

--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU) | Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 250 | 123K| 1950 (1)| 00:00:24 |
| 1 | SORT ORDER BY | | 250 | 123K| 1950 (1)| 00:00:24 |
| 2 | HASH GROUP BY | | 250 | 123K| 1950 (1)| 00:00:24 |
|* 3 | TABLE ACCESS FULL| PUSH_PRED_TAB1 | 250 | 123K| 1948 (1)| 00:00:24 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - filter(TO_NUMBER(TO_CHAR("T0"."ID"))>=1 AND
TO_NUMBER(TO_CHAR("T0"."ID"))<=20)


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
7184 consistent gets
0 physical reads
0 redo size
11132 bytes sent via SQL*Net to client
530 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
20 rows processed

次回へつづく :)

| | コメント (0) | トラックバック (0)

2012年4月 8日 (日)

いん!、イン!、Index どっぷり Inde Only Access生活w - Oracle OpenWorld Unconference presented by JPOUG

Oracle OpenWorld Unconference presented by JPOUG
とこかで聞いたようなタイトルを"インデックス"に置換えた「いん!、イン!、Index どっぷり Inde Only Scan生活w」のセッション資料を公開します。
(相変わらず文才ないな〜〜と落ち込む....作文嫌いは死ぬまで治らないと思ってるが、がんばって書くw)

デモ内容とデモ環境情報を追加予定です。 2012/4/15デモとデモ環境情報追加しました)

直前にデモ環境のOracle11gR2が起動しないという想定外のトラブルを乗り越えw かなりイッパイ、イッパイの状態でしたが楽しい時間を過ごさせていただきました。

関係者の皆様、ご来場の皆様、ありがとうございました。

Safari以外のブラウザではアニメーション効果はありませんが、Safari (Mac/iPad/iPhone)ではKeynote風(但し、ページ間のトランジッションなし)に表示されます。

いん!、イン!、IndexどっぷりIndes Only Scan生活w


(2012/4/10追加)
セッションで利用したデモ環境の情報を載せておきます。

MacBook Air late 2010 13inch 2GB (MacOS X Lion)
VirtualBox4.1.10 for MacOS X
GuestOS:CentOS5.8 x86 
DB : Oracle11g R2 11.2.0.1.0 EE for Linux x86

VMの設定は以下のようになってます。
仮想ディスクはほかにもいろいろやるので合計で21GBほどにしてあります :)

20120410_13346_2

(2012/4/15)host i/o cacheはオフにしてあります。
Vbox_no_hostio_cache

late2010のMBAはメモリが2GBなので、VMには1GBのメモリを割り当ててます。
(オラクルのmemory_targetなどの値も書いたほうがいいのかな〜)

(2012/4/15追記)
メモリサイズがギリギリなのでOracleのメモリ関連初期化パラメータも以下の通りで少なめです。

memory_max_target                    big integer 404M
memory_target big integer 404M
sga_max_size big integer 404M
sga_target big integer 0
pga_aggregate_target big integer 0

(2012/4/15追記)
・デモで使った表これ。

13:56:52 SCOTT> desc tab1
名前 NULL? 型
----------------------------- -------- --------------------
UNIQUE_ID NOT NULL NUMBER
NON_UNIQUE_ID NUMBER(10)
NON_UNIQUE_ID2 NUMBER(5)
DATA VARCHAR2(500)
IS_DELETE NUMBER(1)
STATUS_CODE CHAR(2)

13:56:55 SCOTT> desc tab2
名前 NULL? 型
----------------------------- -------- --------------------
UNIQUE_ID2 NOT NULL NUMBER(5)
DATA VARCHAR2(500)
IS_DELETE NUMBER(1)


こんなデータを登録して...

13:56:58 SCOTT> 
15:01:15 SCOTT> begin for i in 1..100000 loop insert into tab1 values(i,ora_hash(i,999),ora_hash(i,99),lpad(i,500,'*'),0,'00'); end loop; end;
15:01:17 SCOTT> /

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

経過: 00:00:25.46
15:01:44 SCOTT> commit;

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

経過: 00:00:00.01
15:01:59 SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'TAB1',no_invalidate=>false,cascade=>true);

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

経過: 00:00:08.50
15:09:43 SCOTT> begin for i in 1..200 loop insert into tab2 values(i,lpad(i,500,'*'),0); end loop; end;
15:10:12 2 /

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

経過: 00:00:00.05
15:10:13 SCOTT> commit;

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

経過: 00:00:00.08
15:10:16 SCOTT> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'TAB2',no_invalidate=>false,cascade=>true);

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

経過: 00:00:00.99
15:10:50 SCOTT>


データ件数、データのカーディナリティは以下のようになってます。

15:10:50 SCOTT> @show_tab_info
tabnameに値を入力してください: tab1

SEGMENT_NAME SEGMENT_TYPE SEGMENT_SU MB NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR VISIBLE
------------------------------ ------------------ ---------- ---------- ---------- ------------- ----------------- -------
TAB1 TABLE ASSM 62 100000 YES
TAB1_IX1 INDEX ASSM 3 100000 1000 99400 YES
TAB1_IX2 INDEX ASSM 3 100000 100 94181 YES
TAB1_PK INDEX ASSM 2 100000 100000 7693 YES

経過: 00:00:00.78

TABLE_NAME INDEX_NAME COLUMN_NAME DESC DATA_TYPE NUM_NULLS DENSITY NUM_DISTINCT HI_VALUE LO_VALUE HISTOGRAM
------------------------------ ------------------------------ -------------------- ---- ------------ ---------- ---------- ------------ ------------- ------------- ---------------
TAB1 TAB1_IX1 NON_UNIQUE_ID ASC NUMBER 0 .001 1000 999 0 NONE

TAB1_IX2 NON_UNIQUE_ID2 ASC NUMBER 0 5.0182E-06 100 99 0 FREQUENCY

TAB1_PK UNIQUE_ID ASC NUMBER 0 .00001 100000 100000 1 NONE


経過: 00:00:02.51
15:17:47 SCOTT> @show_tab_info
tabnameに値を入力してください: tab2

SEGMENT_NAME SEGMENT_TYPE SEGMENT_SU MB NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR VISIBLE
------------------------------ ------------------ ---------- ---------- ---------- ------------- ----------------- -------
TAB2 TABLE ASSM 1 200 YES
TAB2_PK INDEX ASSM 1 200 200 15 YES

経過: 00:00:00.78

TABLE_NAME INDEX_NAME COLUMN_NAME DESC DATA_TYPE NUM_NULLS DENSITY NUM_DISTINCT HI_VALUE LO_VALUE HISTOGRAM
------------------------------ ------------------------------ -------------------- ---- ------------ ---------- ---------- ------------ ------------- ------------- ---------------
TAB2 TAB2_PK UNIQUE_ID2 ASC NUMBER 0 .005 200 200 1 NONE


経過: 00:00:02.55
15:18:03 SCOTT>


注)毎回buffer cacheをクリア、かつ、ソフトパース時の結果を載せています。

・demo1

通常ならこの実行計画であればなにもしないですよね...

こんな良さげな実行計画でもSQL文と実行計画それに表定義をじ〜〜〜〜〜〜っと見ていると解決策が見えてきますよね。そうindex only accessならね。

14:45:34 SCOTT> 
14:45:34 SCOTT> set autot trace exp stat
14:45:34 SCOTT> @demo1

システムが変更されました。

経過: 00:00:00.03
1 select
2 unique_id
3 ,status_code
4 from
5 tab1
6 where
7 unique_id between 1 and 1000
8* and is_delete = 0

998行が選択されました。

経過: 00:00:00.07

実行計画
----------------------------------------------------------
Plan hash value: 1911646434

---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 999 | 10989 | 81 (0)| 00:00:01 |
|* 1 | TABLE ACCESS BY INDEX ROWID| TAB1 | 999 | 10989 | 81 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | TAB1_PK | 1000 | | 4 (0)| 00:00:01 |
---------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("IS_DELETE"=0)
2 - access("UNIQUE_ID">=1 AND "UNIQUE_ID"<=1000)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
210 consistent gets
80 physical reads
0 redo size
19586 bytes sent via SQL*Net to client
1142 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
998 rows processed


is_deleteなんて列を参照するためにだけに行長の長い行を持つ表を、しかも、レンジスキャンしているのでデータアクセス量が多くなっているんですよね。
そこで、index only access

index only accessするための索引を作成してしまえばそれを回避できますよね.

14:45:36 SCOTT> @demo1_ix
create index tab1_ix_demo1 on tab1(unique_id,is_delete,status_code) nologging invisible

索引が作成されました。

経過: 00:00:00.80
alter session set optimizer_use_invisible_indexes=true

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

経過: 00:00:00.01


TABLE ACCESSが消えました!
consistent getsは、1/3に減少しましたよね :) 時間も 70msから10ms程度に改善してます。

14:46:05 SCOTT> @demo1

システムが変更されました。

経過: 00:00:00.04
1 select
2 unique_id
3 ,status_code
4 from
5 tab1
6 where
7 unique_id between 1 and 1000
8* and is_delete = 0

998行が選択されました。

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 327556384

----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 999 | 10989 | 4 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| TAB1_IX_DEMO1 | 999 | 10989 | 4 (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("UNIQUE_ID">=1 AND "IS_DELETE"=0 AND "UNIQUE_ID"<=1000)
filter("IS_DELETE"=0)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
70 consistent gets
4 physical reads
0 redo size
19586 bytes sent via SQL*Net to client
1142 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
998 rows processed


次のデモのために作成した索引を削除しておきます。

14:46:13 SCOTT> @drop_demo1_ix
drop index tab1_ix_demo1

索引が削除されました。

経過: 00:00:00.61
alter session set optimizer_use_invisible_indexes=false

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

経過: 00:00:00.00

・demo2

demo1同様に行長の長い行をある程度の範囲でレンジスキャンかつ、group/order by句があるのでソート処理が行われています。

これも通常ならチューニングなんてしないのですが、大人の事情で何かできないか? 

と聞かれればindex only accessにして表をアクセスしない+ソート処理も省略しましょうか! と返してあげることが多いです。

14:47:05 SCOTT> @demo2

システムが変更されました。

経過: 00:00:00.02
1 select
2 non_unique_id2
3 ,count(1)
4 from
5 tab1
6 where
7 non_unique_id = 70
8 and is_delete = 0
9 group by
10 non_unique_id2
11 order by
12* non_unique_id2

経過: 00:00:00.06

実行計画
----------------------------------------------------------
Plan hash value: 1524069294

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 63 | 630 | 102 (1)| 00:00:02 |
| 1 | SORT GROUP BY | | 63 | 630 | 102 (1)| 00:00:02 |
|* 2 | TABLE ACCESS BY INDEX ROWID| TAB1 | 100 | 1000 | 101 (0)| 00:00:02 |
|* 3 | INDEX RANGE SCAN | TAB1_IX1 | 100 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("IS_DELETE"=0)
3 - access("NON_UNIQUE_ID"=70)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
103 consistent gets
103 physical reads
0 redo size
517 bytes sent via SQL*Net to client
416 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed


index only scanにしてソート処理も省略するための索引を作成します。

ではどのような索引を作成すればよいのでしょう?

実行計画のPredicate Informationセクションにはどのような索引を作成するかを決定するための重要な情報がリストされています。

気づきましたか?

以下の2行が重要。

2 - filter("IS_DELETE"=0)
3 - access("NON_UNIQUE_ID"=70)

NON_UNIQUE_ID列に作成されている索引を利用して行を取得し、IS_DELETE=0 でフィルタ処理を実施していることがわかります。

ということは、 NON_UNIQUE_ID,IS_DELETEの2列(つまりWHERE句に記述されている検索条件)がアクセスパスですよね。

次に、選択リスト/GROUP BY句/ORDER BY句を見ると NON_UNIQUE_ID2毎のCOUNT()を取りNON_UNIQUE_ID2を昇順にソートしていることがわかります。

NON_UNIQUE_ID,IS_DELETE列アクセスして、NON_UNIQUE_ID2が昇順にソート済みとなっている索引があればindex only accessになるはず!

14:47:14 SCOTT> @demo2_ix
create index tab1_ix_demo2 on tab1(non_unique_id,is_delete,non_unique_id2) logging invisible

索引が作成されました。

経過: 00:00:00.63
alter session set optimizer_use_invisible_indexes=true

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

経過: 00:00:00.01


結果は...

index range scanのみになり、SORT GROUP BY NOSORTというオペレーションからもわかるように、ソート処理が省略されています。;)

consistent getsは約1/50、処理時間は60msから10msまで改善しました。

14:47:28 SCOTT> @demo2

システムが変更されました。

経過: 00:00:00.03
1 select
2 non_unique_id2
3 ,count(1)
4 from
5 tab1
6 where
7 non_unique_id = 70
8 and is_delete = 0
9 group by
10 non_unique_id2
11 order by
12* non_unique_id2

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 2668135026

--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 63 | 630 | 1 (0)| 00:00:01 |
| 1 | SORT GROUP BY NOSORT| | 63 | 630 | 1 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | TAB1_IX_DEMO2 | 100 | 1000 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("NON_UNIQUE_ID"=70 AND "IS_DELETE"=0)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2 consistent gets
2 physical reads
0 redo size
517 bytes sent via SQL*Net to client
416 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

作成した索引を削除して次のデモへ。

14:47:33 SCOTT> @drop_demo2_ix
drop index tab1_ix_demo2

索引が削除されました。

経過: 00:00:00.10
alter session set optimizer_use_invisible_indexes=false

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

経過: 00:00:00.00

・demo3

これdemo2の応用編です. 行長の長い行をある程度の範囲でレンジ検索し、最小値(min)を取得するものです。最大値(max)でも考え方は同じ。

14:47:49 SCOTT> @demo3

システムが変更されました。

経過: 00:00:00.04
1 select
2 min(non_unique_id2)
3 from
4 tab1
5 where
6 non_unique_id = 70
7* and is_delete = 0

経過: 00:00:00.05

実行計画
----------------------------------------------------------
Plan hash value: 255634452

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | 101 (0)| 00:00:02 |
| 1 | SORT AGGREGATE | | 1 | 10 | | |
|* 2 | TABLE ACCESS BY INDEX ROWID| TAB1 | 100 | 1000 | 101 (0)| 00:00:02 |
|* 3 | INDEX RANGE SCAN | TAB1_IX1 | 100 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("IS_DELETE"=0)
3 - access("NON_UNIQUE_ID"=70)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
103 consistent gets
103 physical reads
0 redo size
456 bytes sent via SQL*Net to client
416 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed


demo2と同様に、WHERE句で参照されている検索条件列がアクセスパスになるような索引に、選択リスト中のmin()関数で参照されている列を追加した索引でindex only accessになりそうですよね。

14:47:51 SCOTT> @demo3_ix
create index tab1_ix_demo3 on tab1(non_unique_id, is_delete, non_unique_id2) nologging invisible

索引が作成されました。

経過: 00:00:00.65
alter session set optimizer_use_invisible_indexes=true

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

経過: 00:00:00.00


結果は、狙い通りになりました!

処理時間は40msから10msへ。consistent getsは約1/50に改善しました。

実行計画のINDEX RANGE SCAN (MIN/MAX) 〜 FIRST ROWSという素敵なオペレーションが現れています。

Rowsを見ると索引作成前は 100行選択して1行返すという無駄の多い(99行は捨てている)操作から1行とってきて1行返すというエコな動きをしていることが確認できます! 素敵ですね。

14:48:05 SCOTT> @demo3

システムが変更されました。

経過: 00:00:00.04
1 select
2 min(non_unique_id2)
3 from
4 tab1
5 where
6 non_unique_id = 70
7* and is_delete = 0

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 3012701563

----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | 2 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 10 | | |
| 2 | FIRST ROW | | 1 | 10 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN (MIN/MAX)| TAB1_IX_DEMO3 | 1 | 10 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("NON_UNIQUE_ID"=70 AND "IS_DELETE"=0)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
2 consistent gets
2 physical reads
0 redo size
456 bytes sent via SQL*Net to client
416 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

作詞絵作成した索引を削除して次のデモへ...

14:48:09 SCOTT> @drop_demo3_ix
drop index tab1_ix_demo3

索引が削除されました。

経過: 00:00:00.09
alter session set optimizer_use_invisible_indexes=false

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

経過: 00:00:00.01


・demo4

大人の事情で実行計画だけみてるとチューニングなんて不要でしょ! という感じのクエリをチューニングできないか?

と依頼されたら・・・

2つの表を結合した結果でgorup/order byしているのでソート処理のバイパスはできせんが、index only accessを使ってNested Loop結合でアクセスされるブロック数を最小化することはできますよね。

14:48:26 SCOTT> @demo4

システムが変更されました。

経過: 00:00:00.12
1 select
2 d.non_unique_id
3 ,m.unique_id2
4 ,count(1)
5 from
6 tab2 m join tab1 d
7 on m.unique_id2 = d.non_unique_id2
8 and m.is_delete = 0
9 and d.is_delete = 0
10 where
11 m.unique_id2 between 1 and 10
12 group by
13 d.non_unique_id
14 ,m.unique_id2
15 order by
16 d.non_unique_id
17* ,m.unique_id2

100行が選択されました。

経過: 00:00:01.71

実行計画
----------------------------------------------------------
Plan hash value: 1194523033

-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1034 | 16544 | 539 (1)| 00:00:07 |
| 1 | SORT GROUP BY | | 1034 | 16544 | 539 (1)| 00:00:07 |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 1034 | 16544 | 537 (0)| 00:00:07 |
|* 4 | TABLE ACCESS BY INDEX ROWID| TAB2 | 10 | 60 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB2_PK | 10 | | 1 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB1_IX2 | 111 | | 1 (0)| 00:00:01 |
|* 7 | TABLE ACCESS BY INDEX ROWID | TAB1 | 103 | 1030 | 106 (0)| 00:00:02 |
-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - filter("M"."IS_DELETE"=0)
5 - access("M"."UNIQUE_ID2">=1 AND "M"."UNIQUE_ID2"<=10)
6 - access("M"."UNIQUE_ID2"="D"."NON_UNIQUE_ID2")
filter("D"."NON_UNIQUE_ID2"<=10 AND "D"."NON_UNIQUE_ID2">=1)
7 - filter("D"."IS_DELETE"=0)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
9604 consistent gets
5793 physical reads
0 redo size
2588 bytes sent via SQL*Net to client
482 bytes received via SQL*Net from client
8 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
100 rows processed


WHERE句やPredicate Informationからも駆動表(TAB2)と内部表(TAB1)は判断できると思います。

駆動表(TAB2)はWHERE句の検索条件および結合条件でアクセスパスとなる列が参照されています。
内部表(TAB1)はWHERE句では参照されておらず、結合条件でのみアクセスパスとなる列が参照されていることがわかります。

選択リストやGROUP/ORDER BY句から、内部表(TAB1)のNON_UNIQUE_ID列も索引に含める必要があるとわかるはずです。

既に気づいた方も多いと思うのですが、index only access化に必要な索引列候補はSQL文だけでも見つけることができるんですよ。

ただ、複雑な結合などが含まれる場合は、実行計画計画やPredicate Informationに有益な情報が載っているので、できれば合わせて見ておくと吉ですね。  

Truth is out there! :) って場合もあるってことで。

14:48:31 SCOTT> @demo4_ix
create index tab2_ix_demo4 on tab2(unique_id2,is_delete) nologging invisible

索引が作成されました。

経過: 00:00:00.06
create index tab1_ix_demo4 on tab1(non_unique_id2, is_delete, non_unique_id) nologging invisible

索引が作成されました。

経過: 00:00:01.21
alter session set optimizer_use_invisible_indexes=true

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

経過: 00:00:00.00


大きなクエリほど効果は絶大ということで。

1710msから50msに改善してます。consistents getsは、1/291に :)

14:49:04 SCOTT> @demo4

システムが変更されました。

経過: 00:00:00.05
1 select
2 d.non_unique_id
3 ,m.unique_id2
4 ,count(1)
5 from
6 tab2 m join tab1 d
7 on m.unique_id2 = d.non_unique_id2
8 and m.is_delete = 0
9 and d.is_delete = 0
10 where
11 m.unique_id2 between 1 and 10
12 group by
13 d.non_unique_id
14 ,m.unique_id2
15 order by
16 d.non_unique_id
17* ,m.unique_id2

100行が選択されました。

経過: 00:00:00.04

実行計画
----------------------------------------------------------
Plan hash value: 1672990879

------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1034 | 16544 | 31 (4)| 00:00:01 |
| 1 | SORT GROUP BY | | 1034 | 16544 | 31 (4)| 00:00:01 |
| 2 | NESTED LOOPS | | 1034 | 16544 | 30 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN| TAB1_IX_DEMO4 | 10314 | 100K| 30 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN| TAB2_IX_DEMO4 | 1 | 6 | 0 (0)| 00:00:01 |
------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("D"."NON_UNIQUE_ID2">=1 AND "D"."IS_DELETE"=0 AND
"D"."NON_UNIQUE_ID2"<=10)
filter("D"."IS_DELETE"=0)
4 - access("M"."UNIQUE_ID2"="D"."NON_UNIQUE_ID2" AND "M"."IS_DELETE"=0)
filter("M"."UNIQUE_ID2"<=10 AND "M"."UNIQUE_ID2">=1)


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
33 consistent gets
31 physical reads
0 redo size
2588 bytes sent via SQL*Net to client
482 bytes received via SQL*Net from client
8 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
100 rows processed

14:49:09 SCOTT> @drop_demo4_ix
drop index tab1_ix_demo4

索引が削除されました。

経過: 00:00:00.11
drop index tab2_ix_demo4

索引が削除されました。

経過: 00:00:00.04
alter session set optimizer_use_invisible_indexes=false

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

経過: 00:00:00.00


Enjoy!



Index Only Access (Index Only Scan) っていいよね(デメリットもあるけどさ) #1
Index Only Access (Index Only Scan) っていいよね(デメリットもあるけどさ) #2

Index Only Scan, JPOUG, OOW, Oracle Database 11g, SQL, VirtualBox, チューニング | | コメント (1) | トラックバック (0)

2012年3月17日 (土)

売れてるんだね。

InnoDB Deep Talk #1に参加してきました。が、勉強会の話は別エントリにします。今日はDB関連書籍の売れ行きの話。

勉強会で、まつのぶさんの本すげ〜売れてるという話を聞いて早速amazonのぞいてみたら…。すごい売れ行きですね。

ちなみに、http://dbstudy.info/files/20120310/mysql_costcalc.pdf sh2ndさんのセッションで出された宿題まだやってません. m(_ _)m

まつのぶさんの本ってamazonの本全体で435位か、すげ〜。

20120317_84208

ミックさんの本、DB部門だと、まつのぶさんに次いで2位、本全体で、1496位か、これまたすげ〜

20120317_84341

ミックさんと小田さんの本、データベース部門だと、トップ10に2冊もランクイン、すげ〜。

20120317_85604



20120317_85620

| | コメント (0) | トラックバック (0)

2012年1月30日 (月)

AWRレポート、AWR SQLレポート一括取得スクリプトを作ったよ。

Statspackレポートもそうなのですが、AWRレポート/AWR SQLレポートも個別に取得していると凄ーく辛いんですよね。一日分出力するとか、AWRレポートで処理時間の長いSQL文のAWR SQLレポートを個別に取得しようとなると...

ただでさえ忙しいのに、AWRレポート取得するのに時間掛けたくないですよね。

ということで、やっつけで作ったのですが、そのまま載せるのもあれなんで、やっつけで作った感じを多少減らしてgithubに公開しました。;)

https://github.com/discus/Oracle-AWR-batch-generation-script/blob/master/awrreport_batch.sql


もっといい感じに改造してくれるといいな〜とかとか... :)

Oracle11g R1/R2 Enterprise Edition、HTML形式で出力します。(RAC環境では試してないので多分だめかと。)。
AWRを利用するには追加ライセンスが必要なのでご注意を


使い方は・・

SQL*plusを起動し、select any dictionaryシステム権限、dbms_repositoryパッケージの実行権限が付与されたユーザで接続して実行するだけ。
SYSTEMユーザでやる事が多いけど、所変わればなんとやらなので・・・そこんとこよろしく。(w


一括取得なので実行当日を含めてn日分のAWRレポートを取得し、同時に処理時間の長いTop20のAWR SQLレポートも取得します。
レポートは各スナップショット間(今のところ固定)で取得します。

指定するパラメータは、以下の3つ。

Enter snap_id for starting AWR report generation. [NULL] : 
AWRレポートを取得する最初のSNAP_IDを指定します。 NULLがデフォルトでほとんどの場合デフォルトのままで事足りると思います。


Enter number of days for reporting period. [1] : 

一括取得する日数を指定します。当日を含みます。
当日分のAWRレポートを出力するのであれば、デフォルト値の1のままでOKです。


Enter suffix for AWR reports filename. [NULL] : test

保存するAWRレポートのファイル名に付加するsuffixを指定します。
試験名とか設定するといいですよね。

"test"と指定した場合

awrrpt_nnnn_nnnn_test.htmlや
awrsqrpt_nnnn_nnnn_test_sqlid.html

の形式で保存します。(nnnnはSNAP_ID)


実行例1)当日分の全レポートを取得する例

SYSTEM> 
SYSTEM> !ls -l awr*.sql
-rw-r--r-- 1 oracle oinstall 6065 1月 29 18:23 awrreport_batch.sql

SYSTEM> @awrreport_batch
--
-- Oracle AWR and AWR SQL report batch generation script
--
-- ***** This script always generate html format AWR reports. *****
--
Enter snap_id for starting AWR report generation. [NULL] :
Enter number of days for reporting period. [1] :
Enter suffix for AWR reports filename. [NULL] : test
--
--
--
clear break compute;
repfooter off;
ttitle off;
btitle off;

・・・中略・・・

<p />
<br /><a class="awr" href="#top">Back to Top</a><p />
</body></html>

SYSTEM>


実行例2)当日分かつsnap_id=291以降で一括取得。(事前にsnap_idを調べておいてね)

SYSTEM> 
SYSTEM> @awrreport_batch
--
-- Oracle AWR and AWR SQL report batch generation script
--
-- ***** This script always generate html format AWR reports. *****
--
Enter snap_id for starting AWR report generation. [NULL] : 291
Enter number of days for reporting period. [1] :
Enter suffix for AWR reports filename. [NULL] : test
--
--
--
clear break compute;
repfooter off;
ttitle off;
btitle off;

・・・中略・・・

<p />
<br /><a class="awr" href="#top">Back to Top</a><p />
</body></html>

SYSTEM>
SYSTEM>
SYSTEM> !ls -l *.html
-rw-r--r-- 1 oracle oinstall 379083 1月 29 22:23 awrrpt_291_292_test.html
-rw-r--r-- 1 oracle oinstall 11899 1月 29 22:23 awrsqrpt_291_292_test_0c83z9rqx45hu.html
-rw-r--r-- 1 oracle oinstall 11899 1月 29 22:23 awrsqrpt_291_292_test_0h3mfbzk6uyw0.html
-rw-r--r-- 1 oracle oinstall 11897 1月 29 22:23 awrsqrpt_291_292_test_2p7t0mw7zvu5z.html

・・・中略・・・

-rw-r--r-- 1 oracle oinstall 11899 1月 29 22:23 awrsqrpt_291_292_test_bhtycgwkxhfj9.html
-rw-r--r-- 1 oracle oinstall 11900 1月 29 22:23 awrsqrpt_291_292_test_bpaggvtfkar9k.html
-rw-r--r-- 1 oracle oinstall 11899 1月 29 22:23 awrsqrpt_291_292_test_c50hdbyuwhfn6.html
-rw-r--r-- 1 oracle oinstall 11892 1月 29 22:23 awrsqrpt_291_292_test_g3f3cw3zy5aat.html

SYSTEM>

なお、Oracleインスタンスが再起動された期間でawrrpt.sqlなどを実行すると、レポートが作成できずエラーでSQL*Plusも終了してしまいますが、本スクリプトでは該当部分のレポートはスキップするようにしてあります。:)

Instance     DB Name      Snap Id   Snap Started       Level
------------ ------------ --------- ------------------ -----
lampeye LAMPEYE 274 29 1月 2012 09:33 1
275 29 1月 2012 10:00 1
276 29 1月 2012 10:30 1
277 29 1月 2012 11:00 1
278 29 1月 2012 11:30 1
279 29 1月 2012 12:00 1
280 29 1月 2012 12:30 1
281 29 1月 2012 13:00 1
282 29 1月 2012 13:30 1
283 29 1月 2012 14:00 1
284 29 1月 2012 14:30 1
285 29 1月 2012 15:00 1
286 29 1月 2012 15:30 1
287 29 1月 2012 16:00 1
288 29 1月 2012 16:30 1
289 29 1月 2012 17:00 1
290 29 1月 2012 17:30 1

291 29 1月 2012 18:31 1 ←再起動されてる
292 29 1月 2012 18:34 1

293 29 1月 2012 22:26 1 ←再起動されてる


Enjoy!

| | コメント (0) | トラックバック (0)

2012年1月 6日 (金)

Index Only Access (Index Only Scan) っていいよね(デメリットもあるけどさ) #2

Index Only Accessのいいとこ、紹介しちゃいますの続きです。

前回は、索引しかアクセスしない(Index Only Access)場合と、索引+表データもアクセスしちゃう場合の実行計画上の違いを確認しましたよね。


今回は、Index Only Accessで得られる改善効果の1つであるソート処理の回避について簡単な例で確認してみます。

※VISIBLE/INVISIBLEにしている索引の詳細は前回の記事を参照してくださいね。

Now Playing ♪ - ハイスクールララバイ / イモ欽トリオ - 1981

まず、最初は、悪い子の例から。

索引を全表走査した上で order by seq# でソート処理が実行されます。酷いですね。検索条件列に適切な索引を作れよ〜〜〜っ。という状態ですね。

SQL> alter index tab10_i01 invisible;

索引が変更されました。

SQL> alter index tab10_i02 invisible;

索引が変更されました。

SQL> select seq# from tab10 where non_unique_key = '0000000001' order by seq#;

10行が選択されました。

経過: 00:00:04.99

実行計画
----------------------------------------------------------
Plan hash value: 2100371779

----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 160 | 16788 (1)| 00:03:22 |
| 1 | SORT ORDER BY | | 10 | 160 | 16788 (1)| 00:03:22 |
|* 2 | TABLE ACCESS FULL | TAB10 | 10 | 160 | 16787 (1)| 00:03:22 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("NON_UNIQUE_KEY"='0000000001')


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
61544 consistent gets
61540 physical reads
0 redo size
655 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
10 rows processed

次に、普通の子の例。

検索条件であるnon_unique_keyに定義された索引を使いIndex range scanかつ、seq#をアクセスするために表データをrowidでアクセス。その後にソート処理が行われています。

普通の子らしい、よい実行計画ですね。 :)

SQL> alter index tab10_i01 visible;

索引が変更されました。

SQL> alter index tab10_i02 invisible;

索引が変更されました。

SQL> select seq# from tab10 where non_unique_key = '0000000001' order by seq#;

10行が選択されました。

経過: 00:00:00.08

実行計画
----------------------------------------------------------
Plan hash value: 1783972576

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 160 | 5 (20)| 00:00:01 |
| 1 | SORT ORDER BY | | 10 | 160 | 5 (20)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| TAB10 | 10 | 160 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB10_I01 | 10 | | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - access("NON_UNIQUE_KEY"='0000000001')


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
4 consistent gets
17 physical reads
0 redo size
655 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
10 rows processed

そして、最後は、良い子の登場。

この子、データ量が多くなった場合、ソート処理も足枷になると考えたようで Index only accessを利用したソート処理回避作戦を取ったようです。 
実行計画をみればわかりますが、索引しかアクセスしておらず、SORT ORDER BY というオペレーションも消えています!

検索条件である、non_unique_key列とソート対象のseq#列の2列からなる複合索引を利用するIndex only accessを狙ったようですね。

ただし、これには order by seq# [asc]であるという大前提があります。
order by seq# desc というソートも考慮する必要がある場合には、non_unique_key [asc]とseq# desc とした別の複合索引必要になります。(デメリットといえばデメリットですかね、)

order by seq# descというソート条件が仕様上無い事を事前に確認しておけば、なお完璧ですよね〜。 良い子。流石です。 :)

SQL> alter index tab10_i01 invisible;

索引が変更されました。

SQL> alter index tab10_i02 visible;

索引が変更されました。

SQL> select seq# from tab10 where non_unique_key = '0000000001' order by seq#;

10行が選択されました。

経過: 00:00:00.01

実行計画
----------------------------------------------------------
Plan hash value: 489654843

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 160 | 3 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| TAB10_I02 | 10 | 160 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("NON_UNIQUE_KEY"='0000000001')


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
4 consistent gets
16 physical reads
0 redo size
655 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
10 rows processed


次回へつづく。(他のソート回避の例も紹介しちゃうか、考えちう)


Index Only Access (Index Only Scan) っていいよね(デメリットもあるけどさ) #1

いん!、イン!、Index どっぷり Inde Only Access生活w - Oracle OpenWorld Unconference presented by JPOUG

| | コメント (0) | トラックバック (0)

2012年1月 4日 (水)

Index Only Access (Index Only Scan) っていいよね(デメリットもあるけどさ) #1

さて、OracleさんがAppleさんのOSで楽しいことしてくれないから最近つまんなくなりつつあるので、普通にSQL文のチューニングネタです。


鬼熱かった、鬼熱かった! :: Insight out 2011- DB tech showcaseでもかなり触れていた、Index Only Scan日本ではこの表現が割合的に多いようなのですが、Index Only Accessって言ってましたね、トムカイトさんも。英語では後者のほうが一般的なのかもしれません。ここではIndex Only Accessってことにしておきます。;)


デメリットもあるけど、Index Only Accessのいいとこばかりを中心に、書いちゃうよ〜w


まず、前提から。

TAB10表は以下のように定義してあります。意図的に表データが大きくなるようにしてあります :)

 名前                NULL?    型
------------------ -------- --------------
SEQ# NOT NULL NUMBER
NON_UNIQUE_KEY NOT NULL CHAR(10)
DATA VARCHAR2(500)

また、以下のような索引を事前に索引しているが、PK_TAB10という主キー索引以外はINVISIBLEとして作成してある。
INVISIBLEで作成しておくと、索引は通常通りメンテナンスされるが、オプティマイザは実行計画作成時にINVISIBLEな索引を利用しないというOracle11gから登場した便利な機能

また前述の表には以下のような主キー(PK_TAB10)と非ユニークな索引が2つ作成してあります。(あまり良い例ではないですがご勘弁を)
但し、TAB10_I01、TAB10_I02の2索引は、INVISIBLEで作成してあります。効果確認時など便利ですよね。
(不可視索引の詳細はマニュアル「Oracle Database 管理者ガイド 11gリリース1(11.1)不可視索引の作成」を参照のこと。


INDEX_NAME COLUMN_NAME
------------ -------------------
PK_TAB10 SEQ#

TAB10_I01 NON_UNIQUE_KEY

TAB10_I02 NON_UNIQUE_KEY
SEQ#

検証時の処理時間及び、実行統計は、各クエリを2回実行し2回目の処理時間及び、実行統計を載せてあります。
(2回目の実行前にバッファキャッシュをクリアしてあるので、ソフトパース+キャッシュミスほぼ100%という状況の処理時間及び実行統計情報です。)

テストデータは以下件数で、non_unique_keyは偏りはなく均一に分布させあります。
実際のはなし、均等になることの方が稀ではあると思いますけど、Index Only Accessの効果を見る事ができればそれでOKなので。

COUNT(1)
----------
800000


COUNT(DISTINCTNON_UNIQUE_KEY)
-----------------------------
80000


NON_UNIQUE COUNT(1)
---------- ----------
0000000001 10
0000000002 10
0000000003 10
0000000004 10
0000000005 10
0000000006 10
0000000007 10
0000000008 10
0000000009 10
0000000010 10
0000000011 10
0000000012 10
0000000013 10
0000000014 10
0000000015 10
0000000016 10
0000000017 10
0000000018 10
0000000019 10
0000000020 10

・・・以下略・・・

まず最初は一番分かりやすい、Index Only Accessの例から。

non_unique_key列に、TAB10_I01という非ユニーク索引を作成してありますが、現状、INVISIBLE状態にしてあるため索引が無く全表走査となっていますよね。

SQL> set autot trace exp stat
SQL>
SQL> select count(non_unique_key) from tab10 where non_unique_key between '0000000001' and '0000000010';

経過: 00:00:04.32

実行計画
----------------------------------------------------------
Plan hash value: 1584905094

----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 11 | 16787 (1)| 00:03:22 |
| 1 | SORT AGGREGATE | | 1 | 11 | | |
|* 2 | TABLE ACCESS FULL| TAB10 | 100 | 1100 | 16787 (1)| 00:03:22 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("NON_UNIQUE_KEY"<='0000000010' AND
"NON_UNIQUE_KEY">='0000000001')


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
61544 consistent gets
61539 physical reads
0 redo size
562 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

ここで、TAB10_I01索引をVISIBLEへ変更して同一クエリを実行してみると……
TAB10_I01索引を利用した実行計画はIndex Range Scanに変化します。しかも、表データはアクセスしていません。これがIndex Only Accessの典型的な例です。アクセスブロック数も処理時間も大きく改善していますよね。

ただ、Index only scanにはデメリットもあります。

それは、索引が多くなればなるほどDML文には足枷になり遅くなるため点です。参照と更新、挿入、削除のバランスを取るのが大切ですが、
とにかく参照を速くする、更新、挿入、削除の処理性能などは少々犠牲にしても問題ないのであれば、どんどんやっちゃいます!(ご利用は計画的にw)

SQL> alter index tab10_i01 visible;
SQL>
SQL> select count(non_unique_key) from tab10 where non_unique_key between '0000000001' and '0000000010';

経過: 00:00:00.02

実行計画
----------------------------------------------------------
Plan hash value: 3770330610

-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU) | Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 11 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 11 | | |
|* 2 | INDEX RANGE SCAN| TAB10_I01 | 100 | 1100 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("NON_UNIQUE_KEY">='0000000001' AND
"NON_UNIQUE_KEY"<='0000000010')


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
3 physical reads
0 redo size
562 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

ちなみに、クエリは少々違うのですが、Index only accessで無い場合は、index range scan+table access by index rowidとなり以下のような実行計画になっちゃいます。
(こちらの方がよく目にする実行計画じゃないでしょうかね。私も性能的な問題等なければ以下のような実行計画であれば良しとしておくことが多いのも事実です。。)

実行計画
----------------------------------------------------------
Plan hash value: 1097902029

------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 51700 | 11 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID | TAB10 | 100 | 51700 | 11 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | TAB10_I02 | 100 | | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------------

次回へつづく。(忙しくてなかなか書けないかもw)

| | コメント (0) | トラックバック (0)

2011年11月 8日 (火)

オープンソースカンファレンス2011.DB に参加した


osc2011db

OSC.DBって随分ご無沙汰してたと思ったら、前回参加したOSC.DBは、2007年でしたか。息子ちゃんが生まれた年です:)

OSC.DBも2008年以来、久々に開催でしたし、久々にお会いできた方々もいて :) でした。


全てのセッションには参加できなかったので、午前中だけ。

参加したセッションは

  • PostgreSQL 9.1 and more - 日本PostgreSQLユーザ会/永安 悟史
  • OSSDB MySQL - 日本MySQLユーザ会/とみた まさひろ・須藤 功平
  • Windowsで使う! Firebird !! - Firebird日本ユーザー会/木村 明治と 愉快な仲間

とOSC.DBではおなじみのユーザ(ー)会のセッションでした。

PostgreSQL 9.1 は Insight outで聞けなかったところ?を駆け足で聴いた感じ。
(9.1からサポートされたIndes only scanはOracleでもよく使うチューニング方法ですね。あとは、カスケードレプリケーションとか、pg_basebackup関連とか)

MySQLは、MySQL5.6の話とストレージエンジンである groongaストレージエンジン関連をこれまた駆け足で。
(InnoDBオプティマイザ統計情報の永続化、デッドロックをエラーログに出力とか、groongaストレージエンジンには、Spiderエンジンの斯波さんも関わっているとか、MariaDBにバンドルされることになったとか、http://labs.mysql.com/ とか)

30分ぐらいだとどうしても駆け足になってしまいますよね。皆さん早口ですよね。
(いままでで一番高速な語りは、大規模Web サイトでのMySQL導入方法および事例紹介」セミナーの松信さんだったように思います。速すぎて日本語聞き取れねーと思ったのは人生初。おおげさかw)

お腹が減ってきたランチ前のセッションは、Firebird日本ユーザー会。ユーザーなんですよユーザじゃなくて。

木村さんと、林さんの軽快なトークが炸裂(アドリブだったらしいw) したFirefoxじゃなくてFirebirdの話。
(ブラジルではFirebird関連イベントで800人ぐらい軽く集まるらしい。すげー)


そして、ほぼパーフェクトな記録発見。 Thx (やっぱ、iPhoneだけだとメモれないというかつぶやききれないw のだ)
http://emasaka.blog65.fc2.com/blog-entry-952.html


| | コメント (0) | トラックバック (0)

2011年9月13日 (火)

オラクルは会議室で動いてんじゃな〜〜イ。統計情報で動いてんだ!

と少々無理矢理なタイトル付けてみました:) 今回はチューニングはチューニングでも、statspack.snap()のチューニングのお話。

注)表現はそれなりに脚色してありますw


ある日のこと。

大トラブルなんです〜(こういう人、うまくいってないプロジェクトにはよくいます。全然”大”じゃないのに、もう口癖なんでしょうねw)

statspack.snap()がものすごく遅いんです! なんとかなりませんか?

ん〜〜〜、で、状況を教えてください。

PERFSTAT> 
PERFSTAT> exec statspack.snap(i_snap_level=>7,i_executions_th=>0);

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

経過: 00:00:31.46

・・・・中略・・・・

PERFSTAT> exec statspack.snap(i_snap_level=>7,i_executions_th=>0);

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

経過: 00:06:28.02

説明によると、こんな状況になっていたらしい。最初は30秒ぐらいで終わっていたのに、ここ最近は5分以上になり今日はついに6分超えたとか。

で……statspack.snap()の時間がかかってて試験の消化に支障でてんですよね…とか、

マシン貧弱だからな…とか、

と打ち合わせでも話題になることしばしば…


で、この状態になってからやっと、訪ねてきたわけね.

・・・・マシン環境について、しばし歓談タイム〜・・・・

話していて気づいた!

あ!、その試験環境って、統計情報の自動収集は止められてますよね。しかも試験する時に取得している統計情報って試験用のユーザだけ統計情報収集していますよね〜。

あれだ!、  感のいいかあなたなら気づきましたよね。ね。ね。:)

次のディクショナリを見れば一目瞭然! :)

PERFSTAT> select table_name,num_rows,stale_stats,last_analyzed from user_tab_statistics;

TABLE_NAME NUM_ROWS STA LAST_ANA
------------------------------ ---------- --- --------
STATS$BUFFERED_QUEUES
STATS$BUFFERED_SUBSCRIBERS
STATS$BUFFER_POOL_STATISTICS
STATS$CR_BLOCK_SERVER
STATS$CURRENT_BLOCK_SERVER
STATS$DATABASE_INSTANCE
STATS$DB_CACHE_ADVICE
STATS$DLM_MISC
STATS$DYNAMIC_REMASTER_STATS
STATS$ENQUEUE_STATISTICS
STATS$EVENT_HISTOGRAM

・・・・中略・・・・

STATS$STREAMS_POOL_ADVICE
STATS$SYSSTAT
STATS$SYSTEM_EVENT
STATS$SYS_TIME_MODEL
STATS$TEMPSTATXS
STATS$TEMP_SQLSTATS
STATS$THREAD
STATS$TIME_MODEL_STATNAME
STATS$UNDOSTAT
STATS$WAITSTAT

72行が選択されました。


じゃ〜、統計情報収集しましょーよー、ちゃんと! 

23:58:46 PERFSTAT> execdbms_stats.gather_schema_stats(ownname=>'PERFSTAT',no_invalidate=>false,cascade=>true);

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

経過: 00:00:04.02

試してみて!、 staspack.snap()を!  ね!  前よりすげー速くなった!

23:58:50 PERFSTAT> exec statspack.snap(i_snap_level=>7,i_executions_th=>0);

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

経過: 00:00:01.38


STATSPACK.SNAP()内で実行されてるSQL文にも統計情報は必要ですよーーーーーーーーーーーーと。

ごれぐらい”ー”付けてればわかってくれるかなー:)


一発ネタは、これにて FIN

| | コメント (0) | トラックバック (0)

2011年9月 8日 (木)

悩ませ過ぎは及ばざるがごとし #7 - おまけ

最近、おまけ付けるの好きなので:)

コストベースオプティマイザがどんだけ考えているか、チューニング前とチューニング後(案1と案2)でトレースファイルサイズで比較してみることに(本来それだけで比較できるものではないとは思いますが、あまりにも差があるのでw)


コストベースオプティマイザのトレースは次のようにして取得できますよね(詳細はググってね)

SCOTT> alter session set events='10053 trace name context forever, level 1';

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

経過: 00:00:00.00
SCOTT> SELECT …… 

・・・仲略・・・

5999行が選択されました。

経過: 00:00:53.48

SCOTT> alter session set events='10053 trace name context off';

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

経過: 00:00:00.00

SCOTT>

オプティマイザトレースファイルのサイズを比較すると以下のようになります:) 
オプティマイザを悩ますクエリ、ヒントで考えるな!感じろ!にしたクエリ(考えてますけど〜〜〜w)、最後が、相関副問合せに書き換えたクエリの順となっています。

[oracle@lampeye trace]$ ll
合計 59088
-rw-r----- 1 oracle oinstall 54431 9月 8 22:14 alert_lampeye2.log
-rw-r----- 1 oracle oinstall 54294792 9月 8 22:16 lampeye2_ora_5913.trc
-rw-r----- 1 oracle oinstall 5154103 9月 8 22:17 lampeye2_ora_5969.trc
-rw-r----- 1 oracle oinstall 898699 9月 8 22:19 lampeye2_ora_5981.trc
[oracle@lampeye trace]$

最初のトレースファイル(.trc),54294792バイトもありますよーw このオプティマイザトレースファイルがハードパースで48秒も考え込んでた時のものです。
(対象クエリも合わせて載せてみました)

-rw-r----- 1 oracle oinstall 54294792  9月  8 22:16 lampeye2_ora_5913.trc

こんなにオプティマイザを考えさせちゃうクエリ↓

SELECT
T1.surro_id,
T1.name,
T1.modified
FROM
test T1 JOIN (
SELECT DISTINCT
surro_id,
surro_bcd
FROM
test
) T2
ON
T1.surro_id = T2.surro_id AND
T1.surro_bcd = T2.surro_bcd
JOIN test T3
ON
T1.surro_id = T3.surro_id AND
T1.surro_acd= T3.surro_acd
WHERE
T1.surro_id IN (リテラル,....限界まで)
OR T1.surro_id IN (リテラル,....限界まで

.....以下....すきなだけ繰り返しw

/


2つめのトレースファイル、5154103バイトで、1/10程度になりましたーでも大きいですよね。これはヒントを付加して悩まないようにしてあげたものですが、それでもこんなサイズになってます!

-rw-r----- 1 oracle oinstall  5154103  9月  8 22:17 lampeye2_ora_5969.trc

案2、ヒントを付加したクエリでもこんなにでるのね。

SELECT   
/*+
LEADING(T1 T2 T3)
USE_HASH(T1 T2)
USE_HASH(T1 T3)
INDEX(T1 IX01_TEST)
INDEX(T3 IX01_TEST)
*/
T1.surro_id,
T1.name,
T1.modified
FROM
test T1 JOIN (
SELECT
/*+
MERGE
INDEX(test IX02_TEST)
*/
DISTINCT
surro_id,
surro_bcd
FROM
test
) T2
ON
T1.surro_id = T2.surro_id AND
T1.surro_bcd = T2.surro_bcd
JOIN test T3
ON
T1.surro_id = T3.surro_id AND
T1.surro_acd= T3.surro_acd
WHERE
T1.surro_id IN (リテラル,....限界まで)
OR T1.surro_id IN (リテラル,....限界まで)

.....以下....すきなだけ繰り返しw

/


最後のトレースファイルが(案1)、相関副問合せを利用して改善したクエリをハードパースした際に出力されたトレースファイルです。随分違うもんですねー

-rw-r----- 1 oracle oinstall   898699  9月  8 22:19 lampeye2_ora_5981.trc

案1、処理時間も短いけど、ほんと決断はやって感じですね。

SELECT
T1.surro_id
,T1.name
,T1.modified
FROM
test T1 JOIN (
SELECT DISTINCT
surro_id
,surro_bcd
FROM
test
) T2
ON
T1.surro_id = T2.surro_id AND
T1.surro_bcd = T2.surro_bcd
JOIN test T3
ON
T1.surro_id = T3.surro_id AND
T1.surro_acd= T3.surro_acd
WHERE
EXISTS (
SELECT
1
FROM
test_keys
WHERE
surro_id BETWEEN 1000000001 AND 1000005999
AND surro_id = T1.surro_id
)
/




ここまでのあらずじ

悩ませ過ぎは及ばざるがごとし #1
悩ませ過ぎは及ばざるがごとし #2
悩ませ過ぎは及ばざるがごとし #3
悩ませ過ぎは及ばざるがごとし #4
悩ませ過ぎは及ばざるがごとし #5
悩ませ過ぎは及ばざるがごとし #6

| | コメント (0) | トラックバック (0)

2011年9月 7日 (水)

悩ませ過ぎは及ばざるがごとし #6

前回のつづきです。

ヒントを使ってコストベースオプティマイザを悩ませないという方法で、40秒台っだった処理時間を1秒台にすることに成功しました。

これでも解決は解決なのですが、SQL文単体としては。ただ、インスタンスレベルて考えた場合無駄なメモリ使い過ぎだろという点が気がかり。コストベースオプティマイザが悩まなくなったことでCPUリソースの無駄遣いは回避できましたが、メモリリソースの無駄遣いが凄い。

ということで、どちらの無駄も減らす効果が期待できる案1を…

案1で書き換えるとヒントなしでも随分スッキリしちゃいます。
リテラル値が6000個近くありましたが、その部分を相関副問合せにしてあります。リテラル値の箇所はバインド変数化しちゃえばいいっすね。

SELECT
T1.surro_id
,T1.name
,T1.modified
FROM
test T1 JOIN (
SELECT DISTINCT
surro_id
,surro_bcd
FROM
test
) T2
ON
T1.surro_id = T2.surro_id AND
T1.surro_bcd = T2.surro_bcd
JOIN test T3
ON
T1.surro_id = T3.surro_id AND
T1.surro_acd= T3.surro_acd
WHERE
EXISTS (
SELECT
1
FROM
test_keys
WHERE
surro_id BETWEEN 1000000001 AND 1000005999
AND surro_id = T1.surro_id
)
/

実行してみると… (以下、SQL*Plusで set autot trace exp statコマンド叩いて実行した結果です。なお事前に共有プールはクリアしてあります。)

5999行が選択されました。

経過: 00:00:00.07

実行計画
----------------------------------------------------------
Plan hash value: 3724917145

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5997 | 468K| 144 (3)| 00:00:02 |
| 1 | VIEW | VM_NWVW_1 | 5997 | 468K| 144 (3)| 00:00:02 |
| 2 | HASH UNIQUE | | 5997 | 491K| 144 (3)| 00:00:02 |
|* 3 | HASH JOIN RIGHT SEMI | | 5997 | 491K| 143 (2)| 00:00:02 |
|* 4 | INDEX RANGE SCAN | PK_TEST_KEYS | 5999 | 41993 | 17 (0)| 00:00:01 |
|* 5 | HASH JOIN | | 5997 | 450K| 125 (1)| 00:00:02 |
|* 6 | INDEX RANGE SCAN | IX01_TEST | 5999 | 128K| 20 (0)| 00:00:01 |
|* 7 | HASH JOIN | | 5998 | 322K| 105 (1)| 00:00:02 |
|* 8 | INDEX RANGE SCAN | IX02_TEST | 5999 | 53991 | 19 (0)| 00:00:01 |
| 9 | TABLE ACCESS BY INDEX ROWID| TEST | 5999 | 269K| 85 (0)| 00:00:02 |
|* 10 | INDEX RANGE SCAN | IX02_TEST | 5999 | | 19 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------


SQL_ID        SHARABLE_MEM PERSISTENT_MEM RUNTIME_MEM SQL_TEXT
------------- ------------ -------------- ----------- ------------------------------
52gaz3tjd90dh 43446 15648 14504 SELECT T1.surro_id, T1.name,
T1.modified FROM test T1 jo
in ( SELECT DISTINCT s
urro_id, surro_bcd FROM
test ) T2 ON T1.surro_id
= T2.surro_id AND T1.surro
_bcd = T2.surro_bcd JO
IN test T3 ON T1.sur
ro_id = T3.surro_id AND T1.s
urro_acd= T3.surro_acd WHERE
EXISTS ( SELECT 1 FROM
test_keys WHERE surro_
id BETWEEN 1000000001 AND 1000
005999 AND T1.surro_id = su
rro_id )

きゃほーーーーーーっ。 最高〜〜〜〜っ! :) 言うことナッシングw  

処理時間が、48.83秒 から 0.07秒へ、SHARABLE_MEMが、3318142バイト から 43446バイトに。 でバインド変数化しちゃえば〜〜〜、

同時に多数のセッションから集中して発行されても耐えられそうな〜

最後に、ドヤ顔(キリっ

ほんとうにヤバそうなところはキッチリ潰しておきましょ!

OLTP系で実行計画はよいけど、コストベースオプティマイザを悩ませ過ぎて遅延してたら本末転倒ですよー。と。

ーーーー完ーーーーー


ここまでのあらずじ

悩ませ過ぎは及ばざるがごとし #1
悩ませ過ぎは及ばざるがごとし #2
悩ませ過ぎは及ばざるがごとし #3
悩ませ過ぎは及ばざるがごとし #4
悩ませ過ぎは及ばざるがごとし #5

| | コメント (0) | トラックバック (0)

2011年9月 6日 (火)

悩ませ過ぎは及ばざるがごとし #5

前回のつづきです。

前回は改善案を2つ提示しました。

まず、最初は案2のヒントを使って、コストベースオプティマイザに、「考えるな! 感じろ!」作戦w

元のクエリに次のようにヒントを書き加えちゃいます。
どの索引使えとか、結合方法はハッシュとか、とか、よく見かけるヒントだけです。

SELECT   
/*+
LEADING(T1 T2 T3)
USE_HASH(T1 T2)
USE_HASH(T1 T3)
INDEX(T1 IX01_TEST)
INDEX(T3 IX01_TEST)
*/
T1.surro_id,
T1.name,
T1.modified
FROM
test T1 JOIN (
SELECT
/*+
MERGE
INDEX(test IX02_TEST)
*/
DISTINCT
surro_id,
surro_bcd
FROM
test
) T2
ON
T1.surro_id = T2.surro_id AND
T1.surro_bcd = T2.surro_bcd
JOIN test T3
ON
T1.surro_id = T3.surro_id AND
T1.surro_acd= T3.surro_acd
WHERE
T1.surro_id IN (リテラル,....限界まで)
OR T1.surro_id IN (リテラル,....限界まで)

.....以下....すきなだけ繰り返しw

/

では実行してみると… (以下、SQL*Plusで set autot trace exp statコマンド叩いて実行した結果です。なお事前に共有プールはクリアしてあります。)

経過: 00:00:01.32

実行計画
----------------------------------------------------------
Plan hash value: 3720374973

----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5999 | 456K| 5796 (1)| 00:01:10 |
| 1 | VIEW | VM_NWVW_1 | 5999 | 456K| 5796 (1)| 00:01:10 |
| 2 | HASH UNIQUE | | 5999 | 451K| 5796 (1)| 00:01:10 |
|* 3 | HASH JOIN | | 5999 | 451K| 5795 (1)| 00:01:10 |
| 4 | INLIST ITERATOR | | | | | |
|* 5 | INDEX RANGE SCAN | IX01_TEST | 5999 | 128K| 1935 (1)| 00:00:24 |
|* 6 | HASH JOIN | | 5999 | 322K| 3859 (1)| 00:00:47 |
| 7 | INLIST ITERATOR | | | | | |
| 8 | TABLE ACCESS BY INDEX ROWID| TEST | 5999 | 269K| 2001 (1)| 00:00:25 |
|* 9 | INDEX RANGE SCAN | IX01_TEST | 5999 | | 1935 (1)| 00:00:24 |
| 10 | INLIST ITERATOR | | | | | |
|* 11 | INDEX RANGE SCAN | IX02_TEST | 5999 | 53991 | 1857 (1)| 00:00:23 |
----------------------------------------------------------------------------------------------

やたーーーーーーーーーーーーっ! :) 目標の3秒を下回りましたよー :)

コストベースオプティマイザの悩みを解消してあげることができました…が、例のSHARABLE_MEMが3MB超えちゃってる問題はまだ抱えたままです。><

SQL_ID        SHARABLE_MEM PERSISTENT_MEM RUNTIME_MEM SQL_TEXT
------------- ------------ -------------- ----------- ------------------------------
fbjw4wjdma4f7 3318142 396808 395664 SELECT /*+ LEADING(T1 T2 T3
) USE_HASH(T1 T2) USE_HASH
(T1 T3) INDEX(T1 IX01_TEST)
INDEX(T3 IX01_TEST) */ T1.
surro_id, T1.name, T1.modifi
ed FROM test T1 join ( SELE
CT /*+ MERGE INDEX(
test IX02_TEST) */ DISTI
NCT surro_id, surro_bcd

今回はここまで、次回へつづく。


ここまでのあらずじ

悩ませ過ぎは及ばざるがごとし #1
悩ませ過ぎは及ばざるがごとし #2
悩ませ過ぎは及ばざるがごとし #3
悩ませ過ぎは及ばざるがごとし #4

| | コメント (0) | トラックバック (0)

2011年9月 4日 (日)

悩ませ過ぎは及ばざるがごとし #4

さて、さて、前回のつづきです。

意識的に間を取ったんですが、ちょいと長過ぎたかw


前回、ハードパースに時間がかかってるねーってとこまで確認しましたよね〜

20110824_23733


・1回目のSQLトレース

call     count       cpu    elapsed       disk      query    current        rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 45.18 45.15 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 401 0.04 0.05 0 1897 0 5999
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 403 45.23 45.21 0 1897 0 5999


・2回目のSQLトレース

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 401 0.04 0.04 0 1897 0 5999
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 403 0.04 0.04 0 1897 0 5999


ということでした。

じゃ、こんなにコストペースオプティマイザに考えさせちゃうSQL文でどんなのよー。と。 OLTPなのに分析系のすげー難しそーなSQL文なげてるとか??? 想像しているより現物見た方が早いですw

こんな感じのクエリだんたんよー。

SELECT
T1.surro_id,
T1.name,
T1.modified
FROM
test T1 JOIN (
SELECT DISTINCT
surro_id,
surro_bcd
FROM
test
) T2
ON
T1.surro_id = T2.surro_id AND
T1.surro_bcd = T2.surro_bcd
JOIN test T3
ON
T1.surro_id = T3.surro_id AND
T1.surro_acd= T3.surro_acd
WHERE
T1.surro_id IN (リテラル,....限界まで)
OR T1.surro_id IN (リテラル,....限界まで

.....以下....すきなだけ繰り返しw

/


え、え〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜っ、リテラル値が〜〜〜〜すげ〜〜〜〜〜〜〜ぞ〜〜〜〜〜〜。
クエリ自体はたいしたことないw あの人達がすきそーな自己結合だー。(失礼w)

このクエリを見て、解決策2つをすぐに思いついたあたなあなた!、 そう、あなた、凄いです!
INリストには上限あるからね:)- Oracle® Database SQL言語リファレンス11gリリース2(11.2)式のリスト

このクエリの解決策。

解決案1
一番効果のありそうなの、リテラルを排除して、その部分を副問合せにしちゃう!。

解決案2
コストベースオプティマイザを悩ませない方法って、ありますよね、そう、ヒントを使って、「こんな感じにうごいて!」作戦。

このクエリの一番の問題点は、リテラル値が山ほどあって、似たような索引が沢山あって、しかも、リテラル値だから毎回値が異なる。結果として毎回ハードパースになるので、毎回コストベースオプティマイザが考え過ぎちゃうこと。

解決策は前述の2案のうちいずれか一つなんだけど、おすすめは案1のほう。

その訳は、でかいんです、なにが?

以下を見れば、なに? が でかいかよーくわかりますw

23:49:06 SYS> SELECT SQL_ID,SHARABLE_MEM,PERSISTENT_MEM,RUNTIME_MEM,SQL_TEXT FROM v$sql WHERE SQL_TEXT LIKE '%/*TEST*/%';

SQL_ID SHARABLE_MEM PERSISTENT_MEM RUNTIME_MEM SQL_TEXT
------------- ------------ -------------- ----------- ------------------------------
82q8wxz56y357 3317991 396816 395672 SELECT /*TEST*/ T1.surro_id,
T1.name, T1.modified FROM t
est T1 join ( SELECT DISTIN
CT surro_id, surro_bcd
FROM test ) T2 ON T1.s
urro_id = T2.surro_id AND T
1.surro_bcd = T2.surro_bcd
JOIN test T3 ON

ね、でかいでしょ。SHARABLE_MEMなんて3MBいっちゃってるし、この程度のことするにしてはデカ過ぎw
これって、直ちには影響はないのですが…(どっかで聞いた事ある言い回し><  やはり同じ事するセッション多いとさーボディーブローよ。全部ハードパースだしw


今回はここまで、次回へつづく。


ここまでのあらずじ

悩ませ過ぎは及ばざるがごとし #1
悩ませ過ぎは及ばざるがごとし #2
悩ませ過ぎは及ばざるがごとし #3

| | コメント (0) | トラックバック (0)

2011年8月25日 (木)

悩ませ過ぎは及ばざるがごとし #3

前回の続きだす。(まだGANTZ語が抜けてないw)


前回は状況証拠固めでした。

で、以下のような状況証拠は確認済み。

  • 1回目だけ処理時間が異常に長い。
  • 実行統計(consistent getsなど)から実行時のアクセスブロック数の差異はない。かつ、ブロック数も少ない。
  • 物理読み込みは処理時間には影響していない。

という状況証拠を踏まえ、SQL文の処理フェーズを頭に浮かべて、順序よく考えていくと……

consitents getsの量や処理時間からexecutionフェーズ以降で時間を要しているわけではない……
また、
1回目だけ処理時間が長いが、物理読み込みが影響しているわけではない…

20110824_23033


ということは、1回目だけ実行されるといえばハードパース……この部分の処理時間が長いってことですよね!

20110824_23733

だんだん近づいてきましたね。というこで、次はパース時間が長いという証拠固めを。

パース時間を比較する方法はいくつかあります…。 SQL*Plusのset autot trace exp を使って実行計画だけを確認する方法とか、Explain plan文使うとか、SQLトレースを取るとか。
ようするに、パース時間が確認できる方法であればどんな方法でもOKなんです。


今回は結果の比較もしやすい、SQLトレースで証拠固をしてみます。SQLトレースを取得する方法はdbms_monitorパッケージを利用する方法が簡単ですかね。(他にもありますが)

SCOTT> exec dbms_monitor.session_trace_enable(null,null,true,false,'ALL_EXECUTIONS');

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

SCOTT> SELECT

・・・中略・・・

5999行が選択されました。

経過: 00:00:48.37

SCOTT> exec dbms_monitor.session_trace_disable(null,null);

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

こんな感じでセッション単位でSQLトレースを取得してtkprofで整形するというおなじみのスタイル。


・1回目のSQLトレース

call     count       cpu    elapsed       disk      query    current        rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 45.18 45.15 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 401 0.04 0.05 0 1897 0 5999
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 403 45.23 45.21 0 1897 0 5999


・2回目のSQLトレース

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 401 0.04 0.04 0 1897 0 5999
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 403 0.04 0.04 0 1897 0 5999


予想的中!。 パースに時間を要しているのは明らかですね。

最近のコストベースオプティマイザは賢いです。賢いのでいろいろと考えさせすぎると時間がかかります。(将棋でも相手の手数を読み過ぎなんて…ことありますよね。そんな状況に近いです。)。DWH系のクエリならそんなこともあるでしょうけど、今回問題になっているのはOLTPです、しかも、3秒程度で結果を返してほしいらしいし…

で、どのようにしたらコストベースオプティマイザを"悩ませずに”済むか、そこが今回のポイント。

思い切って、ルールベースにしちゃおーか?。それはNG。確かにパース時間は短くなるでしょうけど…


解決方法を見つけるには問題のクエリがどんなものか見極める必要がありそうですね〜…。どんな方法があるだろー。


今日はここまで、次回へつづく。


ここまでのあらずじ

悩ませ過ぎは及ばざるがごとし #1
悩ませ過ぎは及ばざるがごとし #2

| | コメント (0) | トラックバック (0)

2011年8月24日 (水)

悩ませ過ぎは及ばざるがごとし #2

「一緒に考えてみてくらちい」タイポじゃないだすw GANTZ見てたんでつい。:)

ということで前回のつづきです。

1回目だけ異常に処理時間を要するクエリがあると相談を受けたという前振り覚えてますか?

実行計画
----------------------------------------------------------
Plan hash value: 1865103575

--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5999 | 351K| 5718 (1)| 00:01:09 |
|* 1 | HASH JOIN | | 5999 | 351K| 5718 (1)| 00:01:09 |
| 2 | INLIST ITERATOR | | | | | |
|* 3 | INDEX RANGE SCAN | IX01_TEST | 5999 | 59990 | 1935 (1)| 00:00:24 |
|* 4 | HASH JOIN | | 5999 | 292K| 3782 (1)| 00:00:46 |
| 5 | VIEW | | 5999 | 95984 | 1858 (1)| 00:00:23 |
| 6 | HASH UNIQUE | | 5999 | 53991 | 1858 (1)| 00:00:23 |
| 7 | INLIST ITERATOR | | | | | |
|* 8 | INDEX RANGE SCAN | IX02_TEST | 5999 | 53991 | 1857 (1)| 00:00:23 |
| 9 | INLIST ITERATOR | | | | | |
| 10 | TABLE ACCESS BY INDEX ROWID| TEST | 5999 | 199K| 1923 (1)| 00:00:24 |
|* 11 | INDEX RANGE SCAN | IX02_TEST | 5999 | | 1857 (1)| 00:00:23 |
--------------------------------------------------------------------------------------------

・・・中略・・・

統計
----------------------------------------------------------
1 recursive calls
0 db block gets
1897 consistent gets
0 physical reads
0 redo size
247808 bytes sent via SQL*Net to client
69769 bytes received via SQL*Net from client
401 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5999 rows processed


1回目とそれ以降で実行計画の差異はありません。実行統計上はrecursive callが1から0になっている程度の変化しかありません。

ですが……………………

1回目は、48.83秒なのに、2回目以降は、0.08秒…

1回目の処理時間 (SQL*Plusのset timiing on 及び、set autot trace exp statにて計測)


5999行が選択されました。

経過: 00:00:48.83

2回目以降〜


5999行が選択されました。

経過: 00:00:00.08

実行統計を見ると、consistent gets 1897もそれほど多くないのに1回目だけ処理時間が長い、その差はどこに……


SQL文がどのような順序で処理されるのか、よーーーーーく思い出してください;)

リンク先にあるオラクルのマニュアルの図をざっくりまとめちゃうと以下のような感じ(BINDや、FETCHフェーズが書かれている図もよく見かけますね)


20110823_142232



となっています。


この辺りも一緒に読むといいですよね。わかりやすいです。無料だし:)
門外不出のOracle現場ワザ - 第4章 Oracleデータベースの頭脳 「オプティマイザ」徹底研究
SQL文の処理におけるオプティマイザの役割
Part2 CBOは何を見てどう判断するのか

これまでに確認できた状況証拠

  • 1回目だけ処理時間が異常に長い。
  • 実行統計(consistent getsなど)から実行時のアクセスブロック数の差異はない。かつ、ブロック数も少ない。
  • 物理読み込みは処理時間には影響していない。

これらの状況証拠とSQL文の処理フェーズから導きだせますよね。どこに処理時間を要しているか…………

ほーら、ほら。見えてきました…………………よね? (ヒントだしてますから〜〜:)


ということで、今日はここまで、次回へつづく。




ここまでのあらずじ

悩ませ過ぎは及ばざるがごとし #1

| | コメント (0) | トラックバック (0)

2011年8月23日 (火)

悩ませ過ぎは及ばざるがごとし #1

あるクエリを実行していて、目標時間は3秒程度のオンライン処理向けクエリ、初回だけ異常に遅いんですけど……。 と再び、大人の事情に縛られて心が痣だらけ(どんなだ)な人達からお呼びがかかりました。

こんな感じの表(索引は似たようなのが数多くありそれもイケてないといえばイケテてないが)を自己結合しているだけ(イケてるクエリとか、イケてないクエリだというのは置いといて)で実行計画もそんなに悪くないし、実行統計を見る限り
初回とそれ以降で大きく乖離しているようにも見えません。

表は次のように定義されています。

SCOTT> desc test
名前 NULL? 型
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER(10)
SURRO_ID NOT NULL NUMBER(10)
SURRO_ACD NOT NULL CHAR(2)
SURRO_BCD NOT NULL CHAR(1)
SURRO_CCD NOT NULL CHAR(3)
SURRO_DCD NOT NULL CHAR(5)
NAME NOT NULL VARCHAR2(100)
DESCRIPTION VARCHAR2(300)
CREATED NOT NULL TIMESTAMP(6)
MODIFIED NOT NULL TIMESTAMP(6)

索引は以下のようになってます。

INDEX_NAME                     COLUMN_NAME
------------------------------ ------------------------------
IX01_TEST SURRO_ID
SURRO_ACD

IX02_TEST SURRO_ID
SURRO_BCD

IX03_TEST SURRO_ID
SURRO_CCD

IX04_TEST SURRO_ID
SURRO_DCD

PK_TEST ID

なんだか、どれも似たような索引がならんでいます。

INDEX_NAME                     INDEX_TYPE                    NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR
------------------------------ --------------------------- ---------- ------------- -----------------
IX04_TEST NORMAL 700000 700000 7653
IX03_TEST NORMAL 700000 700000 7653
IX02_TEST NORMAL 700000 700000 7653
IX01_TEST NORMAL 700000 700000 7653
PK_TEST NORMAL 700000 700000 7653





どーーーーーーーしてでしょう。

このつづきは次回のお楽しみ。

一緒に考えてみてくらちい ;)


1回目の処理行数、処理時間、実行計画及び実行統計。


・・・中略・・・

5999行が選択されました。

経過: 00:00:48.83

実行計画
----------------------------------------------------------
Plan hash value: 1865103575

--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5999 | 351K| 5718 (1)| 00:01:09 |
|* 1 | HASH JOIN | | 5999 | 351K| 5718 (1)| 00:01:09 |
| 2 | INLIST ITERATOR | | | | | |
|* 3 | INDEX RANGE SCAN | IX01_TEST | 5999 | 59990 | 1935 (1)| 00:00:24 |
|* 4 | HASH JOIN | | 5999 | 292K| 3782 (1)| 00:00:46 |
| 5 | VIEW | | 5999 | 95984 | 1858 (1)| 00:00:23 |
| 6 | HASH UNIQUE | | 5999 | 53991 | 1858 (1)| 00:00:23 |
| 7 | INLIST ITERATOR | | | | | |
|* 8 | INDEX RANGE SCAN | IX02_TEST | 5999 | 53991 | 1857 (1)| 00:00:23 |
| 9 | INLIST ITERATOR | | | | | |
| 10 | TABLE ACCESS BY INDEX ROWID| TEST | 5999 | 199K| 1923 (1)| 00:00:24 |
|* 11 | INDEX RANGE SCAN | IX02_TEST | 5999 | | 1857 (1)| 00:00:23 |
--------------------------------------------------------------------------------------------

・・・中略・・・

統計
----------------------------------------------------------
1 recursive calls
0 db block gets
1897 consistent gets
0 physical reads
0 redo size
247808 bytes sent via SQL*Net to client
69769 bytes received via SQL*Net from client
401 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5999 rows processed



2回目の処理行数、処理時間、実行計画及び実行統計。

1回目とくらべて随分処理時間が短いです。。。。

・・・中略・・・

5999行が選択されました。

経過: 00:00:00.08

実行計画
----------------------------------------------------------
Plan hash value: 1865103575

--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5999 | 351K| 5718 (1)| 00:01:09 |
|* 1 | HASH JOIN | | 5999 | 351K| 5718 (1)| 00:01:09 |
| 2 | INLIST ITERATOR | | | | | |
|* 3 | INDEX RANGE SCAN | IX01_TEST | 5999 | 59990 | 1935 (1)| 00:00:24 |
|* 4 | HASH JOIN | | 5999 | 292K| 3782 (1)| 00:00:46 |
| 5 | VIEW | | 5999 | 95984 | 1858 (1)| 00:00:23 |
| 6 | HASH UNIQUE | | 5999 | 53991 | 1858 (1)| 00:00:23 |
| 7 | INLIST ITERATOR | | | | | |
|* 8 | INDEX RANGE SCAN | IX02_TEST | 5999 | 53991 | 1857 (1)| 00:00:23 |
| 9 | INLIST ITERATOR | | | | | |
| 10 | TABLE ACCESS BY INDEX ROWID| TEST | 5999 | 199K| 1923 (1)| 00:00:24 |
|* 11 | INDEX RANGE SCAN | IX02_TEST | 5999 | | 1857 (1)| 00:00:23 |
--------------------------------------------------------------------------------------------

・・・中略・・・

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
1897 consistent gets
0 physical reads
0 redo size
247808 bytes sent via SQL*Net to client
69766 bytes received via SQL*Net from client
401 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5999 rows processed


3回目の処理行数、処理時間、実行計画及び実行統計。

3回目も2回目と同じようです。1回目だけなぜか異常に遅い。ちなみに、Oracle11g R2 11.2.0.1.0 for Linux x86_64です。


・・・中略・・・

5999行が選択されました。

経過: 00:00:00.08

実行計画
----------------------------------------------------------
Plan hash value: 1865103575

--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5999 | 351K| 5718 (1)| 00:01:09 |
|* 1 | HASH JOIN | | 5999 | 351K| 5718 (1)| 00:01:09 |
| 2 | INLIST ITERATOR | | | | | |
|* 3 | INDEX RANGE SCAN | IX01_TEST | 5999 | 59990 | 1935 (1)| 00:00:24 |
|* 4 | HASH JOIN | | 5999 | 292K| 3782 (1)| 00:00:46 |
| 5 | VIEW | | 5999 | 95984 | 1858 (1)| 00:00:23 |
| 6 | HASH UNIQUE | | 5999 | 53991 | 1858 (1)| 00:00:23 |
| 7 | INLIST ITERATOR | | | | | |
|* 8 | INDEX RANGE SCAN | IX02_TEST | 5999 | 53991 | 1857 (1)| 00:00:23 |
| 9 | INLIST ITERATOR | | | | | |
| 10 | TABLE ACCESS BY INDEX ROWID| TEST | 5999 | 199K| 1923 (1)| 00:00:24 |
|* 11 | INDEX RANGE SCAN | IX02_TEST | 5999 | | 1857 (1)| 00:00:23 |
--------------------------------------------------------------------------------------------

・・・中略・・・


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
1897 consistent gets
0 physical reads
0 redo size
247808 bytes sent via SQL*Net to client
69766 bytes received via SQL*Net from client
401 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5999 rows processed

| | コメント (0) | トラックバック (0)

2011年8月11日 (木)

いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング番外編 

おまけのおまけのおまけとは、さすがに書きづらかったのでw 番外編。

いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング8 おまけのおまけで次のように書いていたのを覚えてますか?

"アクセスするブロック数を減らすのはハッシュパーティションのままではもう、むーりーw 限界です。"

なぜ、限界としたか。という点、聞きたくないですか?

聞きたいですよね。:)

理由は次のクエリ(GROUP BYとCUBEでチューニングしたクエリの一番大切部分)と処理時間、実行計画、consistent getsを見ていただければお分かりいただけるかと……  


そーーーなんです。 以下のブロック数と処理時間って、前回、GROUP BY と CUBEだけでチューニングした結果とそっくりですよね。


  1   SELECT
2 SUBSTR(starting_date,1,6) AS month
3 ,shop_code
4 ,SUM(sales_figure) AS sales_figure
5 FROM
6 test2
7 WHERE
8 starting_date BETWEEN '20110101' AND '20110331'
9 GROUP BY
10 SUBSTR(starting_date,1,6)
11* ,shop_code

3900行が選択されました。

経過: 00:00:06.89

実行計画
----------------------------------------------------------
Plan hash value: 2608398149

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8274 | 137K| 82839 (2)| 00:16:35 | | |
| 1 | HASH GROUP BY | | 8274 | 137K| 82839 (2)| 00:16:35 | | |
| 2 | PARTITION HASH ALL| | 1703K| 27M| 82788 (2)| 00:16:34 | 1 | 4 |
|* 3 | TABLE ACCESS FULL| TEST2 | 1703K| 27M| 82788 (2)| 00:16:34 | 1 | 4 |
---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

3 - filter("STARTING_DATE"<='20110331' AND "STARTING_DATE">='20110101')


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
302341 consistent gets
302289 physical reads
0 redo size
87392 bytes sent via SQL*Net to client
3369 bytes received via SQL*Net from client
261 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
3900 rows processed

と、いうことで限界! と判断したわけなんです。 どうですか? スッキリしましたか? :) では、また。


これまでのあらずじ…

いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #1
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #2
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #3
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #4
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #5
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #6
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #7 おまけ
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング8 おまけのおまけ

| | コメント (0) | トラックバック (0)

2011年8月 6日 (土)

いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング8 おまけのおまけ

前回のつづきです。

GROUPING SETSを単純に使っただけでは可読性は向上するが内部で自動的に行われる一時表への展開(WITH句で書くのに似ている)とUNION ALLによる集計時オーバーヘッドが大きくなるようで、WITH句+UNION ALLに書き換えた場合ほど効果はありませんでした。残念。

だったら〜。

ということで、GROUPING SETSは使わず、やってる事がやってる事だから、素直にCUBEを使ってクロス集計しちゃえばオーバーヘッドとなっているWITH句+UNION ALLへの展開も行わないようにすればなって良いんジャマイカ! そうしよう!


で、パーティションは大人の事情でハッシュのままに、WITH句+UNION ALLへの書き換えや、GROUPING SETSで自動的にWITH句+UNION ALLへ展開する方法を止めたのが以下のクエリ。

え〜〜〜〜〜、え〜〜〜〜〜〜。シリアル実行としては最速の結果、6秒台キタ〜〜〜〜!。
可読性は副問合せなどの影響でちょいと落ちますがSQL文は短いのでそれほど影響はないですよね。いままで一番短いのではないでしょうか:)

  1  SELECT
2 CASE
3 WHEN grouping_id(month) = 1 THEN
4 CASE
5 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0406' THEN 'Q1'
6 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0709' THEN 'Q2'
7 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '1012' THEN 'Q3'
8 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0103' THEN 'Q4'
9 END
10 ELSE month
11 END AS month
12 ,CASE
13 WHEN grouping_id(shop_code) = 1 THEN 'ALL'
14 ELSE shop_code
15 END AS shop_code
16 ,SUM(sales_figure) AS sales_figure
17 FROM
18 (
19 SELECT
20 SUBSTR(starting_date,1,6) AS month
21 ,shop_code
22 ,SUM(sales_figure) AS sales_figure
23 FROM
24 test2
25 WHERE
26 starting_date BETWEEN '20110101' AND '20110331'
27 GROUP BY
28 SUBSTR(starting_date,1,6)
29 ,shop_code
30 )
31 GROUP BY
32 CUBE(month,shop_code)
33 HAVING
34 shop_code = '1000'
35 OR grouping_id(shop_code) = 1
36 ORDER BY
37 month
38* ,shop_code

8行が選択されました。

経過: 00:00:06.86

実行計画
----------------------------------------------------------
Plan hash value: 994924564

---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1381 | 34525 | 83173 (2)| 00:16:39 | | |
| 1 | SORT ORDER BY | | 1381 | 34525 | 83173 (2)| 00:16:39 | | |
|* 2 | FILTER | | | | | | | |
| 3 | SORT GROUP BY | | 1381 | 34525 | 83173 (2)| 00:16:39 | | |
| 4 | GENERATE CUBE | | 1381 | 34525 | 83173 (2)| 00:16:39 | | |
| 5 | SORT GROUP BY | | 1381 | 34525 | 83173 (2)| 00:16:39 | | |
| 6 | VIEW | | 2758 | 68950 | 83172 (2)| 00:16:39 | | |
| 7 | HASH GROUP BY | | 2758 | 66192 | 83172 (2)| 00:16:39 | | |
| 8 | PARTITION HASH ALL | | 394K| 9242K| 83161 (2)| 00:16:38 | 1 | 4 |
|* 9 | TABLE ACCESS FULL | TEST2 | 394K| 9242K| 83161 (2)| 00:16:38 | 1 | 4 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("SHOP_CODE"='1000' OR GROUPING_ID(BIN_TO_NUM(SYS_OP_GROUPING("SHOP_CODE",1,0,
SYS_OP_BITVEC)))=1)
9 - filter("STARTING_DATE"<='20110331' AND SUBSTR("STARTING_DATE",1,6)<='201103' AND
SUBSTR("STARTING_DATE",1,6)>='201101' AND "STARTING_DATE">='20110101')


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
302587 consistent gets
302556 physical reads
0 redo size
871 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
8 rows processed

お約束、オラクルお任せのパラレル実験。 

お〜〜〜〜〜〜〜〜〜〜〜〜っ。キター、1.2秒台〜〜〜〜〜。 パーティションタイプのミスという痛恨の設計ミスを帳消にしてあげたかも。(それは嘘。
そもそも、アクセスするブロック数を減らすのはハッシュパーティションのままではもう、むーりーw 限界です。

  1  SELECT /*+ PARALLEL */
2 CASE
3 WHEN grouping_id(month) = 1 THEN
4 CASE
5 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0406' THEN 'Q1'
6 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0709' THEN 'Q2'
7 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '1012' THEN 'Q3'
8 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0103' THEN 'Q4'
9 END
10 ELSE month
11 END AS month
12 ,CASE
13 WHEN grouping_id(shop_code) = 1 THEN 'ALL'
14 ELSE shop_code
15 END AS shop_code
16 ,SUM(sales_figure) AS sales_figure
17 FROM
18 (
19 SELECT
20 SUBSTR(starting_date,1,6) AS month
21 ,shop_code
22 ,SUM(sales_figure) AS sales_figure
23 FROM
24 test2
25 WHERE
26 starting_date BETWEEN '20110101' AND '20110331'
27 GROUP BY
28 SUBSTR(starting_date,1,6)
29 ,shop_code
30 )
31 GROUP BY
32 CUBE(month,shop_code)
33 HAVING
34 shop_code = '1000'
35 OR grouping_id(shop_code) = 1
36 ORDER BY
37 month
38* ,shop_code

8行が選択されました。

経過: 00:00:01.24

実行計画
----------------------------------------------------------
Plan hash value: 3457173925

-----------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1381 | 34525 | 5773 (2)| 00:01:10 | | | | | |
| 1 | PX COORDINATOR | | | | | | | | | | |
| 2 | PX SEND QC (ORDER) | :TQ10004 | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,04 | P->S | QC (ORDER) |
| 3 | SORT ORDER BY | | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,04 | PCWP | |
| 4 | PX RECEIVE | | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,04 | PCWP | |
| 5 | PX SEND RANGE | :TQ10003 | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,03 | P->P | RANGE |
|* 6 | FILTER | | | | | | | | Q1,03 | PCWC | |
| 7 | SORT GROUP BY | | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,03 | PCWP | |
| 8 | PX RECEIVE | | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,03 | PCWP | |
| 9 | PX SEND HASH | :TQ10002 | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,02 | P->P | HASH |
| 10 | GENERATE CUBE | | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,02 | PCWC | |
| 11 | SORT GROUP BY | | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,02 | PCWP | |
| 12 | PX RECEIVE | | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,02 | PCWP | |
| 13 | PX SEND HASH | :TQ10001 | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,01 | P->P | HASH |
| 14 | SORT GROUP BY | | 1381 | 34525 | 5773 (2)| 00:01:10 | | | Q1,01 | PCWP | |
| 15 | VIEW | | 2758 | 68950 | 5772 (2)| 00:01:10 | | | Q1,01 | PCWP | |
| 16 | HASH GROUP BY | | 2758 | 66192 | 5772 (2)| 00:01:10 | | | Q1,01 | PCWP | |
| 17 | PX RECEIVE | | 2758 | 66192 | 5772 (2)| 00:01:10 | | | Q1,01 | PCWP | |
| 18 | PX SEND HASH | :TQ10000 | 2758 | 66192 | 5772 (2)| 00:01:10 | | | Q1,00 | P->P | HASH |
| 19 | HASH GROUP BY | | 2758 | 66192 | 5772 (2)| 00:01:10 | | | Q1,00 | PCWP | |
| 20 | PX BLOCK ITERATOR | | 394K| 9242K| 5770 (2)| 00:01:10 | 1 | 4 | Q1,00 | PCWC | |
|* 21 | TABLE ACCESS FULL | TEST2 | 394K| 9242K| 5770 (2)| 00:01:10 | 1 | 4 | Q1,00 | PCWP | |
-----------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

6 - filter("SHOP_CODE"='1000' OR GROUPING_ID(BIN_TO_NUM(SYS_OP_GROUPING("SHOP_CODE",1,0,SYS_OP_BITVEC)))=1)
21 - filter("STARTING_DATE"<='20110331' AND SUBSTR("STARTING_DATE",1,6)<='201103' AND SUBSTR("STARTING_DATE",1,6)>='201101' AND
"STARTING_DATE">='20110101')

Note
-----
- automatic DOP: Computed Degree of Parallelism is 16


統計
----------------------------------------------------------
96 recursive calls
0 db block gets
304109 consistent gets
302556 physical reads
0 redo size
871 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
65 sorts (memory)
0 sorts (disk)
8 rows processed


今一度、最初のイケてないクエリと比較。あ〜、ダメダメだ〜。やっぱり無駄が多すぎですね!

  1  SELECT
2 month
3 ,shop_code
4 ,sales_figure
5 FROM
6 (
7 SELECT
8 SUBSTR(starting_date,1,6) AS month
9 ,shop_code
10 ,SUM(sales_figure) AS sales_figure
11 FROM
12 test2
13 WHERE
14 starting_date BETWEEN '20110101' AND '20110331'
15 AND shop_code = '1000'
16 GROUP BY
17 SUBSTR(starting_date,1,6)
18 ,shop_code
19 UNION ALL
20 SELECT
21 CASE
22 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
23 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
24 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
25 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
26 END AS month
27 ,shop_code
28 ,SUM(sales_figure) AS salles_figure
29 FROM
30 test2
31 WHERE
32 starting_date BETWEEN '20110101' AND '20110331'
33 AND shop_code = '1000'
34 GROUP BY
35 CASE
36 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
37 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
38 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
39 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
40 END
41 ,shop_code
42 UNION ALL
43 SELECT
44 SUBSTR(starting_date,1,6) AS month
45 ,'ALL ' AS shop_code
46 ,SUM(sales_figure) AS sales_figure
47 FROM
48 test2
49 WHERE
50 starting_date BETWEEN '20110101' AND '20110331'
51 GROUP BY
52 SUBSTR(starting_date,1,6)
53 UNION ALL
54 SELECT
55 CASE
56 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
57 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
58 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
59 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
60 END AS month
61 ,'ALL ' AS shop_code
62 ,SUM(sales_figure) AS salles_figure
63 FROM
64 test2
65 WHERE
66 starting_date BETWEEN '20110101' AND '20110331'
67 GROUP BY
68 CASE
69 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
70 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
71 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
72 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
73 END
74 )
75 ORDER BY
76 month
77* ,shop_code

8行が選択されました。

経過: 00:00:20.92

実行計画
----------------------------------------------------------
Plan hash value: 1898009794

-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |Pstart| Pstop|
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 22 | 726 | 166K (2)| 00:33:18 | | |
| 1 | SORT ORDER BY | | 22 | 726 | 166K (2)| 00:33:18 | | |
| 2 | VIEW | | 22 | 726 | 166K (2)| 00:33:18 | | |
| 3 | UNION-ALL | | | | | | | |
| 4 | HASH GROUP BY | | 3 | 72 | 43 (3)| 00:00:01 | | |
| 5 | PARTITION HASH ALL | | 303 | 7272 | 42 (0)| 00:00:01 | 1 | 4 |
| 6 | TABLE ACCESS BY LOCAL INDEX ROWID| TEST2 | 303 | 7272 | 42 (0)| 00:00:01 | 1 | 4 |
|* 7 | INDEX RANGE SCAN | PK_TEST2 | 303 | | 19 (0)| 00:00:01 | 1 | 4 |
| 8 | HASH GROUP BY | | 7 | 119 | 43 (3)| 00:00:01 | | |
| 9 | PARTITION HASH ALL | | 303 | 5151 | 42 (0)| 00:00:01 | 1 | 4 |
| 10 | TABLE ACCESS BY LOCAL INDEX ROWID| TEST2 | 303 | 5151 | 42 (0)| 00:00:01 | 1 | 4 |
|* 11 | INDEX RANGE SCAN | PK_TEST2 | 303 | | 19 (0)| 00:00:01 | 1 | 4 |
| 12 | HASH GROUP BY | | 3 | 57 | 83172 (2)| 00:16:39 | | |
| 13 | PARTITION HASH ALL | | 394K| 7316K| 83161 (2)| 00:16:38 | 1 | 4 |
|* 14 | TABLE ACCESS FULL | TEST2 | 394K| 7316K| 83161 (2)| 00:16:38 | 1 | 4 |
| 15 | HASH GROUP BY | | 9 | 108 | 83172 (2)| 00:16:39 | | |
| 16 | PARTITION HASH ALL | | 394K| 4621K| 83161 (2)| 00:16:38 | 1 | 4 |
|* 17 | TABLE ACCESS FULL | TEST2 | 394K| 4621K| 83161 (2)| 00:16:38 | 1 | 4 |
-------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

7 - access("SHOP_CODE"='1000' AND "STARTING_DATE">='20110101' AND "STARTING_DATE"<='20110331')
filter(SUBSTR("STARTING_DATE",1,6)<='201103' AND SUBSTR("STARTING_DATE",1,6)>='201101')
11 - access("SHOP_CODE"='1000' AND "STARTING_DATE">='20110101' AND "STARTING_DATE"<='20110331')
filter(SUBSTR("STARTING_DATE",1,6)<='201103' AND SUBSTR("STARTING_DATE",1,6)>='201101')
14 - filter("STARTING_DATE"<='20110331' AND SUBSTR("STARTING_DATE",1,6)<='201103' AND
SUBSTR("STARTING_DATE",1,6)>='201101' AND "STARTING_DATE">='20110101')
17 - filter("STARTING_DATE"<='20110331' AND SUBSTR("STARTING_DATE",1,6)<='201103' AND
SUBSTR("STARTING_DATE",1,6)>='201101' AND "STARTING_DATE">='20110101')


統計
----------------------------------------------------------
0 recursive calls
0 db block gets
606978 consistent gets
605112 physical reads
0 redo size
875 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
8 rows processed

前回の結果なのでここには載せませんが、GROUPSING SETSで書き換えた場合や、WITH句+UNION ALLで書き換えた場合でも一時表への書き込み及び一時表からの読み込みは複数回発生し、それがボディーブローのように効いていたんよね。:)

これもお約束、そもそもパーティションタイプが月単位のレンジパーティションだったら〜(タラレバw)どんな結果になるか。

キター、シリアル実行でも4秒台、4秒切りそうな勢いです:)
月単位のレンジパーティションにより無駄なブロックを読み込む必要がなくなったことが効いてますね。

  1  SELECT
2 CASE
3 WHEN grouping_id(month) = 1 THEN
4 CASE
5 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0406' THEN 'Q1'
6 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0709' THEN 'Q2'
7 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '1012' THEN 'Q3'
8 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0103' THEN 'Q4'
9 END
10 ELSE month
11 END AS month
12 ,CASE
13 WHEN grouping_id(shop_code) = 1 THEN 'ALL'
14 ELSE shop_code
15 END AS shop_code
16 ,SUM(sales_figure) AS sales_figure
17 FROM
18 (
19 SELECT
20 SUBSTR(starting_date,1,6) AS month
21 ,shop_code
22 ,SUM(sales_figure) AS sales_figure
23 FROM
24 test3
25 WHERE
26 starting_date BETWEEN '20110101' AND '20110331'
27 GROUP BY
28 SUBSTR(starting_date,1,6)
29 ,shop_code
30 )
31 GROUP BY
32 CUBE(month,shop_code)
33 HAVING
34 shop_code = '1000' OR
35 grouping_id(shop_code) = 1
36 ORDER BY
37 month
38* ,shop_code

8行が選択されました。

経過: 00:00:04.04

実行計画
----------------------------------------------------------
Plan hash value: 2039156429

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 32 | | 52177 (2)| 00:10:27 | | |
| 1 | SORT ORDER BY | | 1 | 32 | | 52177 (2)| 00:10:27 | | |
|* 2 | FILTER | | | | | | | | |
| 3 | SORT GROUP BY | | 1 | 32 | | 52177 (2)| 00:10:27 | | |
| 4 | GENERATE CUBE | | 1 | 32 | | 52177 (2)| 00:10:27 | | |
| 5 | SORT GROUP BY | | 1 | 32 | | 52177 (2)| 00:10:27 | | |
| 6 | VIEW | | 80894 | 2527K| | 52171 (2)| 00:10:27 | | |
| 7 | HASH GROUP BY | | 80894 | 1342K| 428M| 52171 (2)| 00:10:27 | | |
| 8 | PARTITION RANGE ITERATOR | | 15M| 258M| | 20630 (2)| 00:04:08 | 1 | 3 |
|* 9 | TABLE ACCESS FULL | TEST3 | 15M| 258M| | 20630 (2)| 00:04:08 | 1 | 3 |
-----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("SHOP_CODE"='1000' OR GROUPING_ID(BIN_TO_NUM(SYS_OP_GROUPING("SHOP_CODE",1,0,SYS_OP_BITVEC)
))=1)
9 - filter("STARTING_DATE"<='20110331' AND "STARTING_DATE">='20110101')


統計
----------------------------------------------------------
1 recursive calls
0 db block gets
73961 consistent gets
25665 physical reads
0 redo size
871 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
8 rows processed

これまた恒例になった、オラクル任せのパラレル処理〜〜。ついにでました、1秒切りそうな処理時間w

  1  SELECT /*+ PARALLEL */
2 CASE
3 WHEN grouping_id(month) = 1 THEN
4 CASE
5 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0406' THEN 'Q1'
6 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0709' THEN 'Q2'
7 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '1012' THEN 'Q3'
8 WHEN SUBSTR(MIN(month),5,2)||SUBSTR(MAX(month),5,2) = '0103' THEN 'Q4'
9 END
10 ELSE month
11 END AS month
12 ,CASE
13 WHEN grouping_id(shop_code) = 1 THEN 'ALL'
14 ELSE shop_code
15 END AS shop_code
16 ,SUM(sales_figure) AS sales_figure
17 FROM
18 (
19 SELECT
20 SUBSTR(starting_date,1,6) AS month
21 ,shop_code
22 ,SUM(sales_figure) AS sales_figure
23 FROM
24 test3
25 WHERE
26 starting_date BETWEEN '20110101' AND '20110331'
27 GROUP BY
28 SUBSTR(starting_date,1,6)
29 ,shop_code
30 )
31 GROUP BY
32 CUBE(month,shop_code)
33 HAVING
34 shop_code = '1000' OR
35 grouping_id(shop_code) = 1
36 ORDER BY
37 month
38* ,shop_code

8行が選択されました。

経過: 00:00:01.09

実行計画
----------------------------------------------------------
Plan hash value: 4042039911

-------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 32 | | 13834 (2)| 00:02:47 | | | | | |
| 1 | PX COORDINATOR | | | | | | | | | | | |
| 2 | PX SEND QC (ORDER) | :TQ10004 | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,04 | P->S | QC (ORDER) |
| 3 | SORT ORDER BY | | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,04 | PCWP | |
| 4 | PX RECEIVE | | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,04 | PCWP | |
| 5 | PX SEND RANGE | :TQ10003 | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,03 | P->P | RANGE |
|* 6 | FILTER | | | | | | | | | Q1,03 | PCWC | |
| 7 | SORT GROUP BY | | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,03 | PCWP | |
| 8 | PX RECEIVE | | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,03 | PCWP | |
| 9 | PX SEND HASH | :TQ10002 | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,02 | P->P | HASH |
| 10 | GENERATE CUBE | | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,02 | PCWC | |
| 11 | SORT GROUP BY | | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,02 | PCWP | |
| 12 | PX RECEIVE | | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,02 | PCWP | |
| 13 | PX SEND HASH | :TQ10001 | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,01 | P->P | HASH |
| 14 | SORT GROUP BY | | 1 | 32 | | 13834 (2)| 00:02:47 | | | Q1,01 | PCWP | |
| 15 | VIEW | | 80894 | 2527K| | 13831 (2)| 00:02:46 | | | Q1,01 | PCWP | |
| 16 | HASH GROUP BY | | 80894 | 1342K| 428M| 13831 (2)| 00:02:46 | | | Q1,01 | PCWP | |
| 17 | PX RECEIVE | | 80894 | 1342K| | 13831 (2)| 00:02:46 | | | Q1,01 | PCWP | |
| 18 | PX SEND HASH | :TQ10000 | 80894 | 1342K| | 13831 (2)| 00:02:46 | | | Q1,00 | P->P | HASH |
| 19 | HASH GROUP BY | | 80894 | 1342K| 428M| 13831 (2)| 00:02:46 | | | Q1,00 | PCWP | |
| 20 | PX BLOCK ITERATOR | | 15M| 258M| | 5726 (2)| 00:01:09 | 1 | 3 | Q1,00 | PCWC | |
|* 21 | TABLE ACCESS FULL | TEST3 | 15M| 258M| | 5726 (2)| 00:01:09 | 1 | 3 | Q1,00 | PCWP | |
-------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

6 - filter("SHOP_CODE"='1000' OR GROUPING_ID(BIN_TO_NUM(SYS_OP_GROUPING("SHOP_CODE",1,0,SYS_OP_BITVEC)))=1)
21 - filter("STARTING_DATE"<='20110331' AND "STARTING_DATE">='20110101')

Note
-----
- automatic DOP: Computed Degree of Parallelism is 4


統計
----------------------------------------------------------
24 recursive calls
0 db block gets
74326 consistent gets
73923 physical reads
0 redo size
871 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
17 sorts (memory)
0 sorts (disk)
8 rows processed


以上、いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング 無事に終了。(おわらすのか〜w)


おまけのおまけまでチューニングしハッシュパーティションのままでも6秒台までになることは確認しましたが、実際に提示したクエリは、UNION ALLで記述された無駄の多いダメダメクエリをWITH句とUNION ALLに書き換えた8秒台の結果となるクエリでした。

なぜ、今回のおまけのおまけで載せた最速クエリを提示しなかったか。。。。。その理由は・・・・・・、8秒台であればユーザと調整可能なレベルだったから、元クエリを書いた方に書き換え方を伝えやすかった(UNION ALLを残しつつチューニング)という、これまた大人の事情が絡んでいます;)。

いま以上にチューニングできるとはわかっていても、余力を残しつつ許容できるレベルでチューニングを止めるという”チューニングのゴール”は大切ですよ。(チューニングに割ける時間もお金も限りがありますから)

8秒台のクエリもデータ量や処理量が増加した際、再び問題視される時期が来るかもしれません。その時は、GROUP BY+CUBEにすれば喜ばれますよ。月単位のレンジパーティションに切り替えてあげるだけでも喜ばれますよ(より工数掛かりますが)。チューニング余地は見切れていたほうがいいですよね。


追記、なにか忘れてるー、と思ったらPIVOTの事すっかり忘れてたw。どうしようw

さて、次のネタのキーワードは、「悩ませ過ぎは及ばざるがごとし」w にしようかな。




これまでのあらずじ…

いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #1
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #2
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #3
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #4
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #5
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #6
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #7 おまけ

| | コメント (0) | トラックバック (0)

2011年8月 2日 (火)

いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング7 おまけ

前回、思いつきでGROUPING SETSに書き換えたら予想外に結果が悪かったわけですが、よーく考えたら事前にもうちょっとサマってしまえばいけるんじゃないかと気づいて少々書き換えてみました、
悲惨だった前回の結果に比べたら随分改善しましたが、それでも元に戻った程度ですね。(^^;;;;;;

やはり、いくない。

以下、前回、一番処理時間が長くなったケース。単純にGROUPING SETSに書き換えた場合、元のクエリより遅い。アクセスブロック数は3/4程度なんですがね。処理時間がねぇ〜

 1  SELECT
2 CASE
3 WHEN quarter IS NULL THEN month
4 ELSE quarter
5 END AS month
6 ,CASE
7 WHEN grouping_id = 1 THEN 'ALL'
8 ELSE shop_code
9 END AS shop_code
10 ,sales_figure
11 FROM (
12 SELECT
13 grouping_id(shop_code) as grouping_id
14 ,quarter
15 ,month
16 ,shop_code
17 ,SUM(sales_figure) AS sales_figure
18 FROM
19 (
20 SELECT
21 CASE
22 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
23 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
24 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
25 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
26 END AS quarter
27 ,SUBSTR(starting_date,1,6) AS month
28 ,shop_code
29 ,sales_figure
30 FROM
31 test2
32 WHERE
33 SUBSTR(starting_date,1,6) BETWEEN '201101' AND '201103'
34 )
35 GROUP BY GROUPING SETS (
36 (month, shop_code),
37 (quarter, shop_code),
38 (month),
39 (quarter)
40 )
41 )
42 WHERE
43 shop_code = '1000'
44 OR grouping_id = 1
45 ORDER BY
46 month
47* ,shop_code

8行が選択されました。

経過: 00:00:34.88

実行計画
----------------------------------------------------------
Plan hash value: 1500989315

------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 711K| 33M| | 93048 (3)| 00:18:37 | | |
| 1 | SORT ORDER BY | | 711K| 33M| 43M| 93048 (3)| 00:18:37 | | |
| 2 | VIEW | | 711K| 33M| | 84288 (3)| 00:16:52 | | |
| 3 | TEMP TABLE TRANSFORMATION | | | | | | | | |
| 4 | LOAD AS SELECT | SYS_TEMP_0FD9D6636_4A53D793 | | | | | | | |
| 5 | PARTITION HASH ALL | | 15M| 351M| | 84280 (3)| 00:16:52 | 1 | 4 |
|* 6 | TABLE ACCESS FULL | TEST2 | 15M| 351M| | 84280 (3)| 00:16:52 | 1 | 4 |
| 7 | LOAD AS SELECT | SYS_TEMP_0FD9D6637_4A53D793 | | | | | | | |
| 8 | SORT GROUP BY ROLLUP | | 1 | 33 | | 3 (34)| 00:00:01 | | |
| 9 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6636_4A53D793 | 1 | 33 | | 2 (0)| 00:00:01 | | |
| 10 | LOAD AS SELECT | SYS_TEMP_0FD9D6637_4A53D793 | | | | | | | |
| 11 | SORT GROUP BY ROLLUP | | 1 | 23 | | 3 (34)| 00:00:01 | | |
| 12 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6636_4A53D793 | 1 | 23 | | 2 (0)| 00:00:01 | | |
| 13 | VIEW | | 1 | 50 | | 2 (0)| 00:00:01 | | |
|* 14 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6637_4A53D793 | 1 | 50 | | 2 (0)| 00:00:01 | | |
------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

6 - filter(SUBSTR("STARTING_DATE",1,6)<='201103' AND SUBSTR("STARTING_DATE",1,6)>='201101')
14 - filter("SYS_TEMP_0FD9D6637_4A53D793"."C1"='1000' OR
BIN_TO_NUM(SYS_OP_VECBIT(SYS_OP_NUMTORAW("SYS_TEMP_0FD9D6637_4A53D793"."D0"),1))=1)


統計
----------------------------------------------------------
761 recursive calls
52432 db block gets
405879 consistent gets
405750 physical reads
1944 redo size
871 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
8 rows processed

意外な結果になってしまったので、WITH句は利用していないのですが今回試したのは事前にある程度サマったケース。
コードサイズも一番小さくて可読性もよいと思うんだけどね〜。
アクセスしているブロック数は1/2でいい感じなのですが、処理時間は今回ネタにしたクエリ(後述)より少々良い程度。ブロック数は半減したのに処理時間がさほど改善していないのはCPUへの負荷が考えていた以上に高いのでしょうかね。(それはまた別途しらべておきますか)

  1  SELECT
2 CASE
3 WHEN quarter IS NULL THEN month
4 ELSE quarter
5 END AS month
6 ,CASE
7 WHEN grouping_id(shop_code) = 1 THEN 'ALL'
8 ELSE shop_code
9 END AS shop_code
10 ,SUM(sales_figure) AS sales_figure
11 FROM
12 (
13 SELECT
14 CASE
15 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
16 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
17 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
18 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
19 END AS quarter
20 ,SUBSTR(starting_date,1,6) AS month
21 ,shop_code
22 ,SUM(sales_figure) AS sales_figure
23 FROM
24 test2
25 WHERE
26 starting_date BETWEEN '20110101' AND '20110331'
27 GROUP BY
28 CASE
29 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
30 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
31 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
32 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
33 END
34 ,SUBSTR(starting_date,1,6)
35 ,shop_code
36 )
37 GROUP BY GROUPING SETS (
38 (month, shop_code),
39 (quarter, shop_code),
40 (month),
41 (quarter)
42 )
43 HAVING
44 shop_code = '1000'
45 OR grouping_id(shop_code) = 1
46 ORDER BY
47 month
48* ,shop_code

8行が選択されました。

経過: 00:00:19.69

実行計画
----------------------------------------------------------
Plan hash value: 3332072264

--------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2758 | 134K| 83181 (2)| 00:16:39 | | |
| 1 | TEMP TABLE TRANSFORMATION | | | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D66D1_4A54AE09 | | | | | | |
| 3 | HASH GROUP BY | | 17550 | 411K| 83172 (2)| 00:16:39 | | |
| 4 | PARTITION HASH ALL | | 394K| 9242K| 83161 (2)| 00:16:38 | 1 | 4 |
|* 5 | TABLE ACCESS FULL | TEST2 | 394K| 9242K| 83161 (2)| 00:16:38 | 1 | 4 |
| 6 | LOAD AS SELECT | SYS_TEMP_0FD9D66D2_4A54AE09 | | | | | | |
| 7 | SORT GROUP BY ROLLUP | | 1 | 33 | 3 (34)| 00:00:01 | | |
| 8 | TABLE ACCESS FULL | SYS_TEMP_0FD9D66D1_4A54AE09 | 1 | 33 | 2 (0)| 00:00:01 | | |
| 9 | LOAD AS SELECT | SYS_TEMP_0FD9D66D2_4A54AE09 | | | | | | |
| 10 | SORT GROUP BY ROLLUP | | 1 | 23 | 3 (34)| 00:00:01 | | |
| 11 | TABLE ACCESS FULL | SYS_TEMP_0FD9D66D1_4A54AE09 | 1 | 23 | 2 (0)| 00:00:01 | | |
| 12 | SORT ORDER BY | | 2758 | 134K| 3 (34)| 00:00:01 | | |
| 13 | VIEW | | 1 | 50 | 2 (0)| 00:00:01 | | |
|* 14 | TABLE ACCESS FULL | SYS_TEMP_0FD9D66D2_4A54AE09 | 1 | 50 | 2 (0)| 00:00:01 | | |
--------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

5 - filter("SYS_TBL_$2$"."STARTING_DATE"<='20110331' AND SUBSTR("STARTING_DATE",1,6)<='201103' AND
SUBSTR("STARTING_DATE",1,6)>='201101' AND "SYS_TBL_$2$"."STARTING_DATE">='20110101')
14 - filter("SYS_TEMP_0FD9D66D2_4A54AE09"."C1"='1000' OR
BIN_TO_NUM(SYS_OP_VECBIT(SYS_OP_NUMTORAW("SYS_TEMP_0FD9D66D2_4A54AE09"."D0"),1))=1)


統計
----------------------------------------------------------
358 recursive calls
52 db block gets
302715 consistent gets
302587 physical reads
1764 redo size
871 bytes sent via SQL*Net to client
519 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
8 rows processed


前々回、WITH句とunion allだけでチューニングしたクエリと比べてみるとブロック数は同じ程度でもCPUコストの差は実行計画からもある程度読み取れますね(参考程度ですがw)。

  1  WITH
2 t01 AS
3 (
4 SELECT
5 SUBSTR(starting_date,1,6) AS month
6 ,CASE
7 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
8 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
9 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
10 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
11 END AS quarter
12 ,shop_code
13 ,SUM(sales_figure) as sales_figure
14 FROM
15 test2
16 WHERE
17 starting_date BETWEEN '20110101' AND '20110331'
18 GROUP BY
19 SUBSTR(starting_date,1,6)
20 ,SUBSTR(starting_date,5,2)
21 ,shop_code
22 ),
23 t02 AS
24 (
25 SELECT
26 month
27 ,quarter
28 ,shop_code
29 ,sales_figure
30 FROM
31 t01
32 WHERE
33 month BETWEEN '201101' AND '201103'
34 AND shop_code = '1000'
35 )
36 SELECT
37 month
38 ,shop_code
39 ,sales_figure
40 FROM
41 (
42 SELECT
43 month
44 ,shop_code
45 ,SUM(sales_figure) AS sales_figure
46 FROM
47 t02
48 GROUP BY
49 month
50 ,shop_code
51 UNION ALL
52 SELECT
53 quarter AS month
54 ,shop_code
55 ,SUM(sales_figure) AS sales_figure
56 FROM
57 t02
58 GROUP BY
59 quarter
60 ,shop_code
61 UNION ALL
62 SELECT
63 month
64 ,'ALL ' AS shop_code
65 ,SUM(sales_figure) AS sales_figure
66 FROM
67 t01
68 GROUP BY
69 month
70 UNION ALL
71 SELECT
72 quarter AS month
73 ,'ALL ' AS shop_code
74 ,SUM(sales_figure) AS salles_figure
75 FROM
76 t01
77 GROUP BY
78 quarter
79 )
80 ORDER BY
81 month
82* ,shop_code

8行が選択されました。

経過: 00:00:08.35

実行計画
----------------------------------------------------------
Plan hash value: 2861358139

--------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3692 | 118K| 83282 (2)| 00:16:40 | | |
| 1 | TEMP TABLE TRANSFORMATION | | | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D660A_4A53712A | | | | | | |
| 3 | HASH GROUP BY | | 17550 | 411K| 83172 (2)| 00:16:39 | | |
| 4 | PARTITION HASH ALL | | 394K| 9242K| 83161 (2)| 00:16:38 | 1 | 4 |
|* 5 | TABLE ACCESS FULL | TEST2 | 394K| 9242K| 83161 (2)| 00:16:38 | 1 | 4 |
| 6 | LOAD AS SELECT | SYS_TEMP_0FD9D660B_4A53712A | | | | | | |
|* 7 | VIEW | | 17550 | 497K| 17 (0)| 00:00:01 | | |
| 8 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660A_4A53712A | 17550 | 411K| 17 (0)| 00:00:01 | | |
| 9 | SORT ORDER BY | | 3692 | 118K| 93 (8)| 00:00:02 | | |
| 10 | VIEW | | 3692 | 118K| 92 (7)| 00:00:02 | | |
| 11 | UNION-ALL | | | | | | | |
| 12 | HASH GROUP BY | | 2758 | 68950 | 27 (4)| 00:00:01 | | |
| 13 | VIEW | | 17550 | 428K| 26 (0)| 00:00:01 | | |
| 14 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660B_4A53712A | 17550 | 497K| 26 (0)| 00:00:01 | | |
| 15 | HASH GROUP BY | | 920 | 20240 | 27 (4)| 00:00:01 | | |
| 16 | VIEW | | 17550 | 377K| 26 (0)| 00:00:01 | | |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660B_4A53712A | 17550 | 497K| 26 (0)| 00:00:01 | | |
| 18 | HASH GROUP BY | | 13 | 260 | 18 (6)| 00:00:01 | | |
| 19 | VIEW | | 17550 | 342K| 17 (0)| 00:00:01 | | |
| 20 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660A_4A53712A | 17550 | 411K| 17 (0)| 00:00:01 | | |
| 21 | HASH GROUP BY | | 1 | 17 | 18 (6)| 00:00:01 | | |
| 22 | VIEW | | 17550 | 291K| 17 (0)| 00:00:01 | | |
| 23 | TABLE ACCESS FULL | SYS_TEMP_0FD9D660A_4A53712A | 17550 | 411K| 17 (0)| 00:00:01 | | |
--------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

5 - filter("STARTING_DATE"<='20110331' AND SUBSTR("STARTING_DATE",1,6)<='201103' AND
SUBSTR("STARTING_DATE",1,6)>='201101' AND "STARTING_DATE">='20110101')
7 - filter("MONTH">='201101' AND "MONTH"<='201103' AND "SHOP_CODE"='1000')


統計
----------------------------------------------------------
4 recursive calls
28 db block gets
302642 consistent gets
302570 physical reads
1156 redo size
875 bytes sent via SQL*Net to client
519 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
8 rows processed


ということで、おまけはおしまい、次回へつづく。(どーやってまとめんるんだこれw)

追記(2011/8/3)
次回は、今回チューニングしたGROUPING SETSを利用した文をさらにチューニングするというおまけのおまけ。(ほんと、どーやってまとめんだこれ!)


これまでのあらずじ…

いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #1
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #2
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #3
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #4
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #5
いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #6


| | コメント (0) | トラックバック (0)

2011年7月24日 (日)

いろいろと面倒くさい大人の事情縛りのOracleパフォーマンスチューニング #6

2011/7/28追記
GROUPING SETSで異常に遅かったのは使い方わるいなーと m(_ _)m WITH句で一時表1つ使うのと同じぐらいにはなるかもね。と(次回書きます)

前回のつづきでーす。

前回のは、元々がイケテない、かつ、大人の事情でパーティションタイプ変更とか、マテリアライズドビューとか、パラレルクエリーもだーめー。と言いつつ、なんとか、できる限り早くしてくれーという、匂いの元を絶たないで別の匂いでごまかしてくれー的なことだったのですが、いろいろと考えた末、WITH句を使えばなんとかいい感じになりました。でも、元が元だけにほぼ限界ですね。

大人の事情で男らしくずばーっとパーティションタイプ変更することができないとか言ってた方々も、この結果をもって、交渉のテーブルにつけるんじゃないかな。検討祈る :)
この結果で納得してもらえるかは感知しませんからねw 最後の手としてパーティションタイプ変更を残しておいてもいいいと思いますし。;)

ということで、今回は、他の方法との比較編。

前回、grouping setsでもいけそーな感じがすると書いたのですが、grouping setsの場合裏の動きはOracleにお任せなので…

まあ、見てみましょう。


最初は、シンプルにgrouping setsに置き換えて見ました。(深く考えてないので余計な部分はまだまだありそうですがw )

実行計画と処理時間、アクセスブロック数みて少々意外な結果に驚いたんですよ。 

ダメダメだった元のクエリより処理時間は長くなってる><。 アクセスしているブロック数は随分少なくなっているんですが。CPUへの負荷が高いのか(詳細は別途調べるとして)とにかく前より遅くなっちゃNGです。ただ、クエリ自体は随分読みやすい :)

grouping setsで置き換えた今回のクエリ、実行計画を見るとWITH句で書き換えたときのようものにかなり似ていますが、WITH句で書き換えた場合と大きく異なるのは内部で一時表に置き換えられるタイミングにありそうですね。WITH句は本体のクエリとは別で、しかも一番先に実行されますが、以下の実行計画をみると違いますよね。WITH句で書き換えた実行計画と比較するとその違いがよくわかります。

  1  SELECT
2 CASE
3 WHEN quarter IS NULL THEN month
4 ELSE quarter
5 END AS month
6 ,CASE
7 WHEN grouping_id = 1 THEN 'ALL'
8 ELSE shop_code
9 END AS shop_code
10 ,sales_figure
11 FROM (
12 SELECT
13 grouping_id(shop_code) as grouping_id
14 ,quarter
15 ,month
16 ,shop_code
17 ,SUM(sales_figure) AS sales_figure
18 FROM
19 (
20 SELECT
21 CASE
22 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
23 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
24 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
25 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
26 END AS quarter
27 ,SUBSTR(starting_date,1,6) AS month
28 ,shop_code
29 ,sales_figure
30 FROM
31 test2
32 WHERE
33 SUBSTR(starting_date,1,6) BETWEEN '201101' AND '201103'
34 )
35 GROUP BY GROUPING SETS (
36 (month, shop_code),
37 (quarter, shop_code),
38 (month),
39 (quarter)
40 )
41 )
42 WHERE
43 shop_code = '1000'
44 OR grouping_id = 1
45 ORDER BY
46 month
47* ,shop_code

8行が選択されました。

経過: 00:00:34.88

実行計画
----------------------------------------------------------
Plan hash value: 1500989315

------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 711K| 33M| | 93048 (3)| 00:18:37 | | |
| 1 | SORT ORDER BY | | 711K| 33M| 43M| 93048 (3)| 00:18:37 | | |
| 2 | VIEW | | 711K| 33M| | 84288 (3)| 00:16:52 | | |
| 3 | TEMP TABLE TRANSFORMATION | | | | | | | | |
| 4 | LOAD AS SELECT | SYS_TEMP_0FD9D6636_4A53D793 | | | | | | | |
| 5 | PARTITION HASH ALL | | 15M| 351M| | 84280 (3)| 00:16:52 | 1 | 4 |
|* 6 | TABLE ACCESS FULL | TEST2 | 15M| 351M| | 84280 (3)| 00:16:52 | 1 | 4 |
| 7 | LOAD AS SELECT | SYS_TEMP_0FD9D6637_4A53D793 | | | | | | | |
| 8 | SORT GROUP BY ROLLUP | | 1 | 33 | | 3 (34)| 00:00:01 | | |
| 9 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6636_4A53D793 | 1 | 33 | | 2 (0)| 00:00:01 | | |
| 10 | LOAD AS SELECT | SYS_TEMP_0FD9D6637_4A53D793 | | | | | | | |
| 11 | SORT GROUP BY ROLLUP | | 1 | 23 | | 3 (34)| 00:00:01 | | |
| 12 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6636_4A53D793 | 1 | 23 | | 2 (0)| 00:00:01 | | |
| 13 | VIEW | | 1 | 50 | | 2 (0)| 00:00:01 | | |
|* 14 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6637_4A53D793 | 1 | 50 | | 2 (0)| 00:00:01 | | |
------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

6 - filter(SUBSTR("STARTING_DATE",1,6)<='201103' AND SUBSTR("STARTING_DATE",1,6)>='201101')
14 - filter("SYS_TEMP_0FD9D6637_4A53D793"."C1"='1000' OR
BIN_TO_NUM(SYS_OP_VECBIT(SYS_OP_NUMTORAW("SYS_TEMP_0FD9D6637_4A53D793"."D0"),1))=1)


統計
----------------------------------------------------------
761 recursive calls
52432 db block gets
405879 consistent gets
405750 physical reads
1944 redo size
871 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
8 rows processed

GROUPING SETSで内部的に行われる一時表作成の効率が今ひとつなのであれば、やはり明示的にWITH句で宣言してからやったほうがいいのかな〜ということで、WITH句で一番大きな一時表を作ってみます。

お〜〜〜〜〜〜〜〜〜〜っ。随分変りましたねー。

  1  WITH
2 t01 AS
3 (
4 SELECT
5 SUBSTR(starting_date,1,6) AS month
6 ,CASE
7 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
8 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
9 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
10 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
11 END AS quarter
12 ,shop_code
13 ,SUM(sales_figure) as sales_figure
14 FROM
15 test2
16 WHERE
17 starting_date BETWEEN '20110101' AND '20110331'
18 GROUP BY
19  SUBSTR(starting_date,1,6)
20 ,SUBSTR(starting_date,5,2)
21 ,shop_code
22 )
23 SELECT
24 CASE
25 WHEN quarter IS NULL THEN month
26 ELSE quarter
27 END AS month
28 ,CASE
29 WHEN grouping_id = 1 THEN 'ALL'
30 ELSE shop_code
31 END AS shop_code
32 ,sales_figure
33 FROM (
34 SELECT
35 grouping_id(shop_code) as grouping_id
36 ,quarter
37 ,month
38 ,shop_code
39 ,SUM(sales_figure) AS sales_figure
40 FROM
41 t01
42 GROUP BY GROUPING SETS (
43 (month, shop_code),
44 (quarter, shop_code),
45 (month),
46 (quarter)
47 )
48 )
49 WHERE
50 shop_code = '1000'
51 OR grouping_id = 1
52 ORDER BY
53 month
54* ,shop_code

8行が選択されました。

経過: 00:00:11.83

実行計画
----------------------------------------------------------
Plan hash value: 3348003365

----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2758 | 134K| 83181 (2)| 00:16:39 | | |
| 1 | SORT ORDER BY | | 2758 | 134K| 83181 (2)| 00:16:39 | | |
| 2 | VIEW | | 2758 | 134K| 83180 (2)| 00:16:39 | | |
| 3 | TEMP TABLE TRANSFORMATION | | | | | | | |
| 4 | LOAD AS SELECT | SYS_TEMP_0FD9D6650_4A53D793 | | | | | | |
| 5 | HASH GROUP BY | | 17550 | 411K| 83172 (2)| 00:16:39 | | |
| 6 | PARTITION HASH ALL | | 394K| 9242K| 83161 (2)| 00:16:38 | 1 | 4 |
|* 7 | TABLE ACCESS FULL | TEST2 | 394K| 9242K| 83161 (2)| 00:16:38 | 1 | 4 |
| 8 | LOAD AS SELECT | SYS_TEMP_0FD9D6651_4A53D793 | | | | | | |
| 9 | SORT GROUP BY ROLLUP | | 1 | 33 | 3 (34)| 00:00:01 | | |
| 10 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6650_4A53D793 | 1 | 33 | 2 (0)| 00:00:01 | | |
| 11 | LOAD AS SELECT | SYS_TEMP_0FD9D6651_4A53D793 | | | | | | |
| 12 | SORT GROUP BY ROLLUP | | 1 | 23 | 3 (34)| 00:00:01 | | |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6650_4A53D793 | 1 | 23 | 2 (0)| 00:00:01 | | |
| 14 | VIEW | | 1 | 50 | 2 (0)| 00:00:01 | | |
|* 15 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6651_4A53D793 | 1 | 50 | 2 (0)| 00:00:01 | | |
----------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

7 - filter("SYS_TBL_$2$"."STARTING_DATE"<='20110331' AND SUBSTR("STARTING_DATE",1,6)<='201103' AND
SUBSTR("STARTING_DATE",1,6)>='201101' AND "SYS_TBL_$2$"."STARTING_DATE">='20110101')
15 - filter("SYS_TEMP_0FD9D66B3_4A54062A"."C1"='1000' OR
BIN_TO_NUM(SYS_OP_VECBIT(SYS_OP_NUMTORAW("SYS_TEMP_0FD9D66B3_4A54062A"."D0"),1))=1)


統計
----------------------------------------------------------
358 recursive calls
53 db block gets
302715 consistent gets
302589 physical reads
1712 redo size
871 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
8 rows processed


じゃ、WITH句だけで行ったチューニング同様に2つの一時表を作ることにしましょー :)
WITH句で2つの一時表を準備(本体の表へは1度だけアクセス)、かつ、grouping sets を使いunion allの利用は最小限にしてみました。w

読みやすくなったし、速度的にも WITH句で2つの一時表を使う場合と変らないし、GROUPING SETSだけだと今回のような問題は解決しづらいかもしれませんね、今のところ。
可読性の向上にはかなり効果あるとおもいます、GROUPING SETSって。


  1  WITH
2 t01 AS
3 (
4 SELECT
5 SUBSTR(starting_date,1,6) AS month
6 ,CASE
7 WHEN SUBSTR(starting_date,5,2) BETWEEN '04' AND '06' THEN 'Q1'
8 WHEN SUBSTR(starting_date,5,2) BETWEEN '07' AND '09' THEN 'Q2'
9 WHEN SUBSTR(starting_date,5,2) BETWEEN '10' AND '12' THEN 'Q3'
10 WHEN SUBSTR(starting_date,5,2) BETWEEN '01' AND '03' THEN 'Q4'
11 END AS quarter
12 ,shop_code
13 ,SUM(sales_figure) as sales_figure
14 FROM
15 test2
16 WHERE
17 starting_date BETWEEN '20110101' AND '20110331'
18 GROUP BY
19  SUBSTR(starting_date,1,6)
20 ,SUBSTR(starting_date,5,2)
21 ,shop_code
22 ),
23 t02 AS
24 (
25 SELECT
26 month
27 ,quarter
28 ,shop_code
29 ,sales_figure
30 FROM
31 t01
32 WHERE
33 month BETWEEN '201101' AND '201103'
34 AND shop_code = '1000'
35 )
36 SELECT
37 CASE
38 WHEN quarter IS NULL THEN month
39 ELSE quarter
40 END AS month
41 ,shop_code
42 ,sales_figure
43 FROM (
44 SELECT
45 quarter
46 ,month
47 ,shop_code
48 ,SUM(sales_figure) AS sales_figure
49 FROM
50 t02
51 GROUP BY GROUPING SETS (
52 (month, shop_code),
53 (quarter, shop_code)
54 )
55 UNION ALL
56 SELECT
57 quarter
58 ,month
59 ,'ALL' AS shop_code
60 ,SUM(sales_figure) AS sales_figure
61 FROM
62 t01
63 GROUP BY GROUPING SETS (
64 (month),
65 (quarter)
66 )
67 )
68 ORDER BY
69 month
70* ,shop_code

8行が選択されました。

経過: 00:00:08.34

実行計画
----------------------------------------------------------
Plan hash value: 1439899557

------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 17 | 595 | 83223 (2)| 00:16:39 | | |
| 1 | TEMP TABLE TRANSFORMATION | | | | | | | |
| 2 | LOAD AS SELECT | SYS_TEMP_0FD9D667D_4A53D793 | | | | | | |
| 3 | HASH GROUP BY | | 17550 | 411K| 83172 (2)| 00:16:39 | | |
| 4 | PARTITION HASH ALL | | 394K| 9242K| 83161 (2)| 00:16:38 | 1 | 4 |
|* 5 | TABLE ACCESS FULL | TEST2 | 394K| 9242K| 83161 (2)| 00:16:38 | 1 | 4 |
| 6 | SORT ORDER BY | | 17 | 595 | 51 (10)| 00:00:01 | | |
| 7 | VIEW | | 17 | 595 | 50 (8)| 00:00:01 | | |
| 8 | UNION-ALL | | | | | | | |
| 9 | TEMP TABLE TRANSFORMATION | | | | | | | |
| 10 | LOAD AS SELECT | SYS_TEMP_0FD9D667E_4A53D793 | | | | | | |
|* 11 | TABLE ACCESS FULL | SYS_TEMP_0FD9D667D_4A53D793 | 1 | 24 | 17 (0)| 00:00:01 | | |
| 12 | LOAD AS SELECT | SYS_TEMP_0FD9D667F_4A53D793 | | | | | | |
| 13 | HASH GROUP BY | | 1 | 33 | 3 (34)| 00:00:01 | | |
| 14 | TABLE ACCESS FULL | SYS_TEMP_0FD9D667E_4A53D793 | 1 | 33 | 2 (0)| 00:00:01 | | |
| 15 | LOAD AS SELECT | SYS_TEMP_0FD9D667F_4A53D793 | | | | | | |
| 16 | HASH GROUP BY | | 1 | 23 | 3 (34)| 00:00:01 | | |
| 17 | TABLE ACCESS FULL | SYS_TEMP_0FD9D667E_4A53D793 | 1 | 23 | 2 (0)| 00:00:01 | | |
| 18 | VIEW | | 1 | 37 | 2 (0)| 00:00:01 | | |
| 19 | TABLE ACCESS FULL | SYS_TEMP_0FD9D667F_4A53D793 | 1 | 37 | 2 (0)| 00:00:01 | | |
| 20 | TEMP TABLE TRANSFORMATION | | | | | | | |
| 21 | LOAD AS SELECT | SYS_TEMP_0FD9D6680_4A53D793 | | | | | | |
| 22 | TABLE ACCESS FULL | SYS_TEMP_0FD9D667D_4A53D793 | 17550 | 411K| 17 (0)| 00:00:01 | | |
| 23 | LOAD AS SELECT | SYS_TEMP_0FD9D6681_4A53D793 | | | | | | |
| 24 | HASH GROUP BY | | 1 | 27 | 3 (34)| 00:00:01 | | |
| 25 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6680_4A53D793 | 1 | 27 | 2 (0)| 00:00:01 | | |
| 26 | LOAD AS SELECT | SYS_TEMP_0FD9D6681_4A53D793 | | | | | | |
| 27 | HASH GROUP BY | | 1 | 17 | 3 (34)| 00:00:01 | | |
| 28 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6680_4A53D793 | 1 | 17 | 2 (0)| 00:00:01 | | |
| 29 | VIEW | | 1 | 31 | 2 (0)| 00:00:01 | | |
| 30 | TABLE ACCESS FULL | SYS_TEMP_0FD9D6681_4A53D793 | 1 | 31 | 2 (0)| 00:00:01 | | |
------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

5 - filter("STARTING_DATE"<='20110331' AND SUBSTR("STARTING_DATE",1,6)<='201103' AND
SUBSTR("STARTING_DATE",1,6)>='201101' AND "STARTING_DATE">='20110101')
11 - filter("SYS_TBL_$2$"."C0">='201101' AND "SYS_TBL_$2$"."C0"<='201103' AND "SYS_TBL_$2$"."C2"='1000')


統計
----------------------------------------------------------
790 recursive calls
82 db b