とある会社の勉強会

とある会社で勉強会を開催し、その奮闘記を徒然なるままに綴っていきます。

漢のDB講座 番外編

今回は、先日のDB講座でOracleのクエリーリライトに軽く説明してみましたが、
ログにはどのように出力されるのか?と言った質問を受けたので、
調査してみました。

ログと一言で言ってもOracleには様々なログがあります。
REDOログ、監査ログ、アーカイブログ…。

でも、今回の質問の意図がクエリーリライト機能が使われた事を
どのように知れるかってことなので、身近なAUTOTRACEとSQLトレースでどのような
出力になるかの観点で調査しております。

環境は以下の通り。

Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 
- 64bit Production
Windows 2012 R2 (on Windows Azure)

なお、構成については、以下を参照させていただきました。
クエリー・リライト - オラクル・Oracleをマスターするための基本と仕組み


今回利用するテーブル

DESC SALES;

 Name                  Null?    Type
 --------------------- -------- --------------

 USER_NAME                      VARCHAR2(5)
 SALES                          NUMBER
 SALES_DATE                     DATE


サンプルデータとして40万ちょっとの件数を用意しました。

SELECT * FROM SALES;
...
BOB        16300 24-FEB-11
SAM        19300 24-FEB-11

USER_      SALES SALESE_DA
----- ---------- ---------
DAVE       19400 24-FEB-11
CAS        22900 24-FEB-11
TOM        28700 24-FEB-11
BOB        14400 24-FEB-11
SAM        12100 24-FEB-11
DAVE       17600 24-FEB-11
CAS        16300 24-FEB-11
TOM        13600 24-FEB-11
BOB        20000 24-FEB-11
SAM        26400 24-FEB-11
DAVE        1200 08-DEC-10

412280 rows selected.


さて、簡単に現在の状態でAUTOTRACEを確認してみます。

set autotrace on


SELECT
       USER_NAME, SUM(SALES) SUM_SALES
FROM
       SALES
WHERE
       USER_NAME = 'TOM'
GROUP BY
       USER_NAME
;
USER_  SUM_SALES
----- ----------
TOM   1233431500


Execution Plan
----------------------------------------------------------
Plan hash value: 1729043503

------------------------------------------------------------------------------
| Id  | Operation            | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |       |     1 |    17 |     3   (0)| 00:00:01 |
|   1 |  SORT GROUP BY NOSORT|       |     1 |    17 |     3   (0)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL  | SALES |     1 |    17 |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------

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

   2 - filter("USER_NAME"='TOM')


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

まあ、普通にテーブルアクセスフルが出てきます。


続いて、現在の状態でSQLトレースを確認してみます。

ALTER SESSION SET SQL_TRACE = TRUE;

SELECT
       USER_NAME, TRUNC(SALES_DATE, 'MONTH') SALES_DATE, SUM(SALES) SUM_SALES
  FROM
       SALES
GROUP BY
       USER_NAME, TRUNC(SALES_DATE, 'MONTH')
;

ALTER SESSION SET SQL_TRACE = FALSE;

以下、tkprofの結果です。
該当する箇所だけ抜き取ってみます。

SELECT
       USER_NAME, TRUNC(SALES_DATE, 'MONTH') SALES_DATE, SUM(SALES) SUM_SALES
  FROM
       SALES
GROUP BY
       USER_NAME, TRUNC(SALES_DATE, 'MONTH')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.02          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       39      0.29       0.31          0       1213          0         565
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       41      0.29       0.33          0       1213          0         565

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 104  
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max)  Row Source Operation
---------- ---------- ----------  ---------------------------------------------------
       565        565        565  HASH GROUP BY (cr=1213 pr=0 pw=0 time=310196 us cost=3 size=26 card=1)
    412280     412280     412280   TABLE ACCESS FULL SALES (cr=1213 pr=0 pw=0 time=195467 us cost=3 size=26 card=1)

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

ふむふむ、こちらもテーブルフルスキャンになっていますね。


さて、いよいよ書き換え用のマテリアライズドビューを作成します。

CREATE MATERIALIZED VIEW
  SALES_SUM_PRODUCT_MONTH
BUILD IMMEDIATE
REFRESH COMPLETE
ENABLE QUERY REWRITE
AS
SELECT
       USER_NAME, TRUNC(SALES_DATE, 'MONTH') SALES_DATE, SUM(SALES) SUM_SALES
  FROM
       SALES
GROUP BY
       USER_NAME, TRUNC(SALES_DATE, 'MONTH')
;

ENABLE QUERY REWRITE
ってところがクエリーリライトのための記述っすね。


では、実行してみましょう。
セッション内でクエリーリライトの使用を可能にします。

ALTER SESSION SET QUERY_REWRITE_ENABLED=TRUE;


トレースの出力を確認するため、強制的にクエリーリライトを使うよう
ヒント句を付与してSQLを実行します。

SELECT /*+ REWRITE(SALES_SUM_PRODUCT_MONTH) */
       USER_NAME, SUM(SALES) SUM_SALES
  FROM
       SALES
 WHERE
       USER_NAME = 'TOM'
       AND TRUNC(SALES_DATE, 'MONTH') = TO_DATE('2005/10','YYYY/MM')
GROUP BY
       USER_NAME, TRUNC(SALES_DATE, 'MONTH');

AUTOTRACEの実行結果です。

Execution Plan
----------------------------------------------------------
Plan hash value: 2900729471

--------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                    | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                         |     1 |    17 |     3   (0)| 00:00:01 |
|*  1 |  MAT_VIEW REWRITE ACCESS FULL| SALES_SUM_PRODUCT_MONTH |     1 |    17 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------

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

   1 - filter("SALES_SUM_PRODUCT_MONTH"."SALES_DATE"=TO_DATE(' 2005-10-01 00:00:00',
              'syyyy-mm-dd hh24:mi:ss') AND "SALES_SUM_PRODUCT_MONTH"."USER_NAME"='TOM')


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

SQLトレースの結果です。

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

SELECT /*+ REWRITE(SALES_SUM_PRODUCT_MONTH) */
       USER_NAME, SUM(SALES) SUM_SALES
  FROM
       SALES
 WHERE
       USER_NAME = 'TOM'
       AND TRUNC(SALES_DATE, 'MONTH') = TO_DATE('2005/10','YYYY/MM')
GROUP BY
       USER_NAME, TRUNC(SALES_DATE, 'MONTH')

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          5          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          5          0           1

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 104  
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max)  Row Source Operation
---------- ---------- ----------  ---------------------------------------------------
         1          1          1  MAT_VIEW REWRITE ACCESS FULL SALES_SUM_PRODUCT_MONTH (cr=5 pr=0 pw=0 time=53 us cost=3 size=17 card=1)

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

おぉぉ、どちらの結果も「MAT_VIEW REWRITE」「SALES_SUM_PRODUCT_MONTH」
となっていて、クエリーが書き換わったのが出力されている事が分かります。


クエリーリライトはかなり効果的な機能ではありますが、
あくまでも暫定処置、緊急の応急処置と捉えるべきでしょう。
実行されるはずのSQLOracleが書き換えているため、環境が換わったら遅くなったって
こともあり得ます。

修正すべき箇所が分かっていれば、誰が見てもわかるコードにしないと、保守性も低下しますし。


さて、上にも書きましたが、今回、サンプルデータを作成するのに苦労しました。

JDBCの簡単なプログラムでサンプルデータを作ろうと思いましたが、
ローカルからOracleへの接続がうまくできず、結果、ローカルのMySQLにデータを作成し、
強引にINSERT文を作り、Azure上で実行という力技でした。

この辺りも、ちょっと研究をしなければと思う年の瀬でありんす。

では、みなさん Merry X'mas!!