UPDATEとDELETE
UPDATE文とDELETE文の実行計画については以下の例のとおりです。
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 1 | 8 | 1 (0)| 00:00:01 |
| 1 | UPDATE | EMP | | | | |
|* 2 | INDEX UNIQUE SCAN| PK_EMP | 1 | 8 | 0 (0)| 00:00:01 |
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | DELETE STATEMENT | | 1 | 26 | 1 (0)| 00:00:01 |
| 1 | DELETE | EMP | | | | |
|* 2 | INDEX UNIQUE SCAN| PK_EMP | 1 | 26 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------
Operation=UPDATE or DELETE となる Name列が示すものが、対象のテーブル名です。
SELECTと同様にV$SQL_PLANから情報を取得するクエリーは以下のとおりです。
select distinct
SQL_ID
,OBJECT_NAME
,decode(operation,'UPDATE',6
,'DELETE',7) CRUD_TYPE_ID
from V$SQL_PLAN
where OPERATION in ('UPDATE','DELETE')
and OBJECT_OWNER = 'xxxx'
order by
OBJECT_NAME
,CRUD_TYPE_ID
,SQL_ID
;
SELECTも一緒に取得するように書きなおしたSQL文の実行例は以下のとおりです。
SQL> select distinct
2 SQL_ID
3 ,OBJECT_NAME
4 ,decode(operation,'TABLE ACCESS',3
5 ,'UPDATE',6
6 ,'DELETE',7) CRUD_TYPE_ID
7 ,OPTIONS
8 from
9 V$SQL_PLAN
10 where OPERATION in ('TABLE ACCESS','UPDATE','DELETE')
11 and OBJECT_OWNER = 'SCOTT'
12 order by
13 OBJECT_NAME
14 ,CRUD_TYPE_ID
15 ,SQL_ID;
SQL_ID OBJECT_NAME CRUD_TYPE_ID OPTIONS
------------- ------------ ------------ --------
a2dk8bdn0ujx7 EMP 3 FULL
2r07krus3ums3 EMP 6
7hr1wxxb8hkqc EMP 7
INSERTは難しい
INSERT文も同じような要領で情報を取れると思ったのですが、なかなかやっかいです。
以下はINSERT文の実行計画です。
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | 38 | 1 (0)| 00:00:01 |
| 1 | LOAD TABLE CONVENTIONAL | EMP | | | | |
---------------------------------------------------------------------------------
Operation=”LOAD TABLE CONVENTIONAL” となるName列を持ってくればよさそうな気がします。
しかし、いろいろなINSERT文を見ていて次のような実行計画に出くわしました。
--------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | | | | 47068 (100)| |
| 1 | LOAD TABLE CONVENTIONAL | | | | | | |
|* 2 | HASH JOIN | | 22794 | 44M| 44M| 47068 (1)| 00:09:25 |
|* 3 | TABLE ACCESS FULL | Txxxxxxxxxxxxxxxxxxxxxxxx | 22794 | 44M| | 1922 (1)| 00:00:24 |
| 4 | TABLE ACCESS FULL | Mxxxxxxxxxxx | 4861K| 148M| | 32769 (1)| 00:06:34 |
--------------------------------------------------------------------------------------------------------------
これは実際の実行計画のテーブル名をマスクしたものですが、INSERT対象のテーブル名はどこにも出てきていないことがおわかりでしょうか?
つまり、実行計画からINSERT先のテーブルを特定することはできないのです。
正規表現を使う
そこでV$SQLAREAを使って情報を取得することにしました。SQL_TEXT列にSQLテキストの先頭1,000バイトが入っているので、そこから文字列操作でテーブル名を取り出す作戦です。
SUBSTR関数等いろいろ試してみたのですが、最終的に10g以降で使えるようになった正規表現機能で抜き出すことにしました。
以下が情報取得用のクエリーです。
select
SQL_ID
,ltrim(
replace(
upper(
substr(
regexp_substr(SQL_TEXT,'INTO[[:space:]]+[^[:space:]\(]+',1,1,'i')
,6)
)
,'"')
) OBJECT_NAME
,COMMAND_TYPE CRUD_TYPE_ID
,null OPTIONS
from V$SQLAREA
where COMMAND_TYPE = 2
order by
SQL_ID
,OBJECT_NAME
;
解説:
- 7行目がもっとも肝になる部分です。「INTO [テーブル名]」となる文字列をregexp_substr関数で抜き出します。
- substr関数で抜き出した文字列の6バイト目以降をテーブル名として取り出します。(6行目)
- upper関数でテーブル名を大文字に統一します。(5行目)
- たまにテーブル名がダブルクォートで囲まれている場合があるのでreplace関数で取り除きます。これが最終的なOBJECT_NAMEとなります(4行目)
以下は実行例です。
V$SQLAREAにはテーブルの所有者を特定できる情報がないので、上記クエリーの結果とDBA_TABLESをジョインしてOWNER列で絞り込む条件を加えています。
SQL> select distinct
2 SQL_ID
3 ,OBJECT_NAME
4 ,CRUD_TYPE_ID
5 ,OPTIONS
6 from
7 (
8 select
9 SQL_ID
10 ,ltrim(
11 replace(
12 upper(
13 substr(
14 regexp_substr(SQL_TEXT,'INTO[[:space:]]+[^[:space:]\(]+',1,1,'i')
15 ,6)
16 )
17 ,'"')
18 ) OBJECT_NAME
19 ,COMMAND_TYPE CRUD_TYPE_ID
20 ,null OPTIONS
21 from
22 V$SQLAREA
23 where COMMAND_TYPE = 2
24 ) I
25 ,DBA_TABLES D
26 where I.OBJECT_NAME = D.TABLE_NAME
27 and D.OWNER = 'SCOTT'
28 order by
29 SQL_ID
30 ,OBJECT_NAME
31 ;
SQL_ID OBJECT_NAME CRUD_TYPE_ID OPTIONS
------------- ------------------------------ ------------ --------
7071f28x901k7 EMP 2
gjvsdk24ufadk EMP 2
UNIONでまとめてみる
最後に、INSERT/SELECT/UPDATE/DELETE の結果をまとめてみましょう。24行目の「union all」に注目して下さい。
補足すると、CRUD_TYPE_IDをdecode関数で「C」「R」「U」「D」に変換しています。(4行目)
なぜ、このような面倒くさいことをやっているのかというと、最初から「C」「R」「U」「D」という文字列にすると、この順番にソートしてくれないのでCRUD_TYPE_IDという数値を使っているのです。(細かいですが私のこだわりです。)
それから、SQL*Plusコマンドを使って、CRUD別のSQL数も集計してみました。(1-2行目)
SQL> break on crud skip page
SQL> compute count of sql_id on crud
SQL> select
2 decode(CRUD_TYPE_ID,2,'C'
3 ,3,'R'
4 ,6,'U'
5 ,7,'D') CRUD
6 ,OBJECT_NAME
7 ,SQL_ID
8 ,OPTIONS
9 from
10 (
11 select distinct
12 SQL_ID
13 ,OBJECT_NAME
14 ,decode(operation,'TABLE ACCESS',3
15 ,'UPDATE',6
16 ,'DELETE',7) CRUD_TYPE_ID
17 ,OPTIONS
18 from
19 V$SQL_PLAN
20 where OPERATION in ('TABLE ACCESS','UPDATE','DELETE')
21 and OBJECT_OWNER = 'SCOTT'
22 union all
23 select
24 SQL_ID
25 ,ltrim(
26 replace(
27 upper(
28 substr(
29 regexp_substr(SQL_TEXT,'INTO[[:space:]]+[^[:space:]\(]+',1,1,'i')
30 ,6)
31 )
32 ,'"')
33 ) OBJECT_NAME
34 ,COMMAND_TYPE CRUD_TYPE_ID
35 ,null OPTIONS
36 from
37 V$SQLAREA
38 where COMMAND_TYPE = 2
39 ) I
40 ,DBA_TABLES D
41 where I.OBJECT_NAME = D.TABLE_NAME
42 and D.OWNER = 'SCOTT'
43 order by
44 CRUD_TYPE_ID
45 ,OBJECT_NAME
46 ,SQL_ID
47 ;
C OBJECT_NAME SQL_ID OPTIONS
- -------------------- ------------- ---------------
C EMP 7071f28x901k7
EMP gjvsdk24ufadk
* -------------
c 2
C OBJECT_NAME SQL_ID OPTIONS
- -------------------- ------------- ---------------
R EMP 7071f28x901k7 BY INDEX ROWID
EMP a2dk8bdn0ujx7 FULL
EMP b84685tx0a4u0 BY INDEX ROWID
EMP f93ucvwf6d1vh FULL
* -------------
c 4
C OBJECT_NAME SQL_ID OPTIONS
- -------------------- ------------- ---------------
U EMP 2r07krus3ums3
* -------------
c 1
C OBJECT_NAME SQL_ID OPTIONS
- -------------------- ------------- ---------------
D EMP 7hr1wxxb8hkqc
* -------------
c 1
どのテーブル(OBJECT_NAME)がどのSQL(SQL_ID)によってINSERT/SELECT/UPDATE/DELETEの対象になっているかが、この一覧からわかります。
(OPTIONSは単なるおまけですのでなくても構いません)
この例ではSCOTT.EMP表しか出てきていませんが、実際は非常に沢山の結果が返ってくるはずです。
次回は、MS Excelのピボットテーブルを使って CRUD表らしく加工してみます。
(続く)