ここからはExcelで
前回のクエリーの結果をMicrosoft Excelのピボットテーブルを利用して集計してみましょう。(Excel2010での方法を説明します。)
今回はある架空アプリケーションにおける実績データを対象とします。
一番簡単なのはSQL*Plusの実行結果を以下のようにExcelのシートにコピー&ペーストするやり方です。
データをスペースで区切る
データ>区切り位置 で「区切り位置指定ウィザード」を呼び出し、「スペースによって右または左に揃えられた固定長フィールドのデータ」を選択して、データを区切ります。
ピボットテーブルで集計
データを区切ったら、A〜C列(CRUD, SQL_ID, OBJECT_NAME)を選択した状態から、挿入>ピボットテーブル で「ピボットテーブルの作成(ウィザード)」を呼び出します。配置する場所が「新規ワークシート」となっていることを確認し、「OK」をクリックします。
右側にピボットテーブルのフィールドリストを指定する画面が出てくるので、リストのそれぞれを下のボックスにドラッグして指定します。
- レポートフィルタ: CRUD
- 列ラベル: OBJECT_NAME
- 行ラベル: SQL_ID
- 値: CRUD(データの個数となっていることを確認)
これで完成ですが、見やすくするためにテーブル名を90°回転させて列幅を揃える等の整形をします。
CRUD表を使ってみる
作成したCRUD表(モドキですが)は縦にSQL(SQL_ID)、横にテーブル名が並んだ表で、それぞれの交差部分に「1」が立っていれば、そのSQL文で該当するテーブルを使っていることを意味します。
まず、下図のB1カラム(レポートフィルタ)で「R」を選択し、SELECT文を見てみましょう。
1つのSELECT文で参照するテーブル数
例えば、SQL_ID = 2p8c09m8rupuk のSQL文は「ABCD_BHA」「NIC」「STU3」という3つのテーブルをアクセスしています。一番右端の総計欄を見るとSQL文でアクセスしているテーブル数の合計がわかります。(表を横に見ます。)
この合計数が多いSQLはジョインしているテーブルの数が多い可能性が高いため、注目する必要があるものです。
あるテーブルを使用しているSQL文とその数
逆にあるテーブルから関連するSQL文とその数を確認するには、表を縦に見ます。
上図の例では、「EFGH_DAY_OUT」というテーブルは一番下の総計欄が「34」になっています。これは34種類の異なるSQL文で使用されていることを示しています。
この数が多いということは
- 多くのSQL文でアクセスされるホットなテーブルである。
- バインド変数を使わないために類似SQL文が多数存在している。
という2つの可能性を示しています。
いずれにせよ、パフォーマンス・チューニングで注目すべきポイントになりますので、AWRレポート等他の情報と突き合わせて問題の特定を行うようにしましょう。
また、このあるテーブルに関係するSQL文の一覧を得ることは、テーブル定義の変更で影響を与える対象を把握するのにも役立ちます。
SQLテキストと実行計画を確認する
一覧ではSQL文を表示させていません。SQL_IDが特定できたら以下のSQL文を実行してSQLテキストと実行計画を確認します。
set pages 1000 set lines 140 select plan_table_output from table(DBMS_XPLAN.DISPLAY_CURSOR('<SQL_ID>'));
応用編
インデックス – SQL
V$SQL_PLANを使えば、テーブルとSQLの関係だけでなく、インデックスとSQLの関係を分析することもできます。
例えば、あるインデックスの定義を変更しようとする場合、1つのSQLだけに注目してしまうと他のSQLに影響があることに気づかず新たな問題を引き起こしてしまうかもしれません。
そのような場合、インデックスとSQLの相関表が役に立ちます。
DBA_HIST_*を使う
V$SQL_PLANもX$SQLAREAもメモリ(SGA)上にキャッシュされている情報です。従って、共有プールの使用率が高い場合等にはどんどんキャッシュから追い出されてしまい欲しい情報が得られない場合があります。
もし、AWRが使える環境であれば
- V$SQL_PLAN → DBA_HIST_SQL_PLAN
- V$SQLAREA → DBA_HIST_SQLTEXT
に置き換えれば、AWR(SQL)レポート保存期間以内のデータが保証されますので、事後解析でも有用なデータが得られます。
念のためAWR版の情報取得SQLを以下に紹介します。
select decode(CRUD_TYPE_ID,2,'C' ,3,'R' ,6,'U' ,7,'D') CRUD ,SQL_ID ,OBJECT_NAME ,OPTIONS from ( select distinct SQL_ID ,OBJECT_NAME ,decode(operation,'TABLE ACCESS',3 ,'UPDATE',6 ,'DELETE',7) CRUD_TYPE_ID ,OPTIONS from DBA_HIST_SQL_PLAN --V$SQL_PLAN where OPERATION in ('TABLE ACCESS','UPDATE','DELETE') and OBJECT_OWNER ='xxxx' union all select SQL_ID ,ltrim( replace( upper( substr( regexp_substr(DBMS_LOB.SUBSTR(SQL_TEXT, 1000, 1),'INTO[[:space:]]+[^[:space:]\(]+',1,1,'i') ,6) ) ,'"') ) OBJECT_NAME ,COMMAND_TYPE CRUD_TYPE_ID ,null OPTIONS from DBA_HIST_SQLTEXT --V$SQLAREA where COMMAND_TYPE = 2 ) I ,DBA_TABLES D where I.OBJECT_NAME = D.TABLE_NAME and D.OWNER = 'xxxx' order by CRUD_TYPE_ID ,SQL_ID ,OBJECT_NAME ;
最後に注意事項
V$SQL_PLAN等は負荷の高い状況ではアクセスしないことをお勧めします。DBAが手動で負荷状況を見ながら注意深く実行して下さい。
従って、スクリプトを組んで定期的に自動実行するような運用は行わない方が望ましいと思います。