1. 程式人生 > >檢視 v$sql,v$sqlarea,$sqltext,v$sqltext_with_newlines 的差異

檢視 v$sql,v$sqlarea,$sqltext,v$sqltext_with_newlines 的差異

      檢視v$sql,v$sqlarea,v$sqltext,v$sqltext_with_newlines 是幾個經常容易混淆的檢視,主要是提供library cache中當前快取的sql語句的資訊。這幾個檢視都可以提供當前有關sql語句的具體資訊,但稍有差異。本文主要描述其差異並給出例項。

一、sql語句與遊標
      sql語句,這個沒什麼好說的,就是按照sql標準書寫的sql語句
      遊標,包含shared cursor,session cursor,簡單點來理解,一條sql語句對應一個或多個遊標,且一條sql語句至少解析為一個遊標。

      當任一sql語句被解析到shared_pool中之後,必定會產生相應的遊標,有下列三種情形,
          a、存在可完全共享的父遊標
          b、父遊標存在,但是由於執行環境的變化,不得不生存新的子游標
          c、沒有父遊標存在,需要生成全新的遊標
      對於情形a,由於存在可共享的父遊標,也就是說v$sql中必定已經存在一個對應的sql遊標,我們可以查詢到,執行之後對應executions及相關列會發生變化。
      對於情形b或c,sql語句產生的遊標會被新增到v$sql檢視,也即是新增遊標(b為新增子游標,c為新增父遊標)。

      注:在shared_pool由於aged out原則後的sql可能無法在該檢視查詢到,這個是另外一個話題。

二、檢視差異
1、v$sql檢視
      假定使用者A與使用者B都基於自身schema建立了表t
      使用者A釋出查詢select * from t,此時共享池中產生一條與該語句的相關的sql遊標,在v$sql檢視體現(假定為首次執行)
      不久使用者B也發出select * from t的查詢,同上,v$sql中也對應有一條該語句的遊標
      為了便於理解,我們將v$sql檢視中的sql文字稱之為遊標,將v$sqlarea中的sql文字稱為sql語句

2、v$sqlarea
      對於上述情形
      此時v$sqlarea則是對檢視v$sql的一個聚合,也即是相當於對檢視v$sql使用了distinct關鍵字。
      儘管v$sql中出現了兩條select * from t,而v$sqlarea僅為一條
      v$sqlarea提供的是每條sql語句執行的彙總資訊

3、v$sqltext
      該檢視包括Shared pool中SQL語句的完整文字,但一條SQL語句是被分成多個塊來進行儲存的。
      對於比較短的sql語句,一個piece就搞定,對於比較長的sql語句則需要多個piece的結合來完整展現一條真正的sql語句。

4、v$sqltext_with_newlines
      該檢視用於完整儲存SQL語句所有字元,保留SQL語句的回車和製表符。

三、示例說明

1、建立演示環境
SQL> select * from v$version where rownum<2;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production

SQL> create table t ( x varchar2(30) primary key, y int );

SQL> exec dbms_stats.set_table_stats('SCOTT','T', numrows => 1000000, numblks=>100000);

SQL> alter system flush shared_pool;

SQL> select sql_text from v$sql where upper(sql_text) like 'SELECT % T LOOK_FOR_ME%'
  2  and upper(sql_text) not like '%FROM V$SQL%';

no rows selected

2、產生sql遊標
SQL> declare
  2     l_x_number      number;
  3     l_x_string  varchar2(30);
  4  begin
  5     execute immediate 'alter session set optimizer_mode=all_rows';
  6     for x in (select * from t look_for_me where x = l_x_number) loop null; end loop;
  7     for x in (select * from t look_for_me where x = l_x_string) loop null; end loop;
  8     execute immediate 'alter session set optimizer_mode=first_rows';
  9     for x in (select * from t look_for_me where x = l_x_number) loop null; end loop;
 10     for x in (select * from t look_for_me where x = l_x_string) loop null; end loop;
 11  end;
 12  /

PL/SQL procedure successfully completed.

3、分析 
SQL> col sql_text format a55
SQL> set linesize 180
SQL> col plan_table_output format a80 truncate
SQL> col sql_id new_val sql_id
SQL> select sql_id, sql_text from v$sql where upper(sql_text) like 'SELECT % T LOOK_FOR_ME %B1_';

SQL_ID        SQL_TEXT
------------- -------------------------------------------------------
1qqtru155tyz8 SELECT * FROM T LOOK_FOR_ME WHERE X = :B1
1qqtru155tyz8 SELECT * FROM T LOOK_FOR_ME WHERE X = :B1
1qqtru155tyz8 SELECT * FROM T LOOK_FOR_ME WHERE X = :B1
1qqtru155tyz8 SELECT * FROM T LOOK_FOR_ME WHERE X = :B1

--從上面的查詢可知,對於上面的SQL語句我們得到了相同的SQL_ID。這是因為SQL_ID是由SQL文字hash得到的一個值
--只要SQL文字相同(完全相同),則SQL_ID一定是相同的。

--我們從v$sql視圖裡邊查詢得到了四條相同sql_id的sql語句,也即是四個不同的遊標
--為什麼同樣的sql文字產生了四個不同的遊標呢?這是因為:
-- cursor 1) 使用ALL_ROWS 優化器模式, 繫結變數為number型別
-- cursor 2) 使用ALL_ROWS 優化器模式, 繫結變數為varchar2型別
-- cursor 3) 使用FIRST_ROWS 優化器模式, 繫結變數為number型別  
-- cursor 4) 使用FIRST_ROWS 優化器模式,繫結變數為varchar2型別

--查詢v$sql檢視
SQL> select sql_id,loaded_versions,executions,optimizer_mode, plan_hash_value,child_number,child_address
  2  from v$sql where sql_id = '&sql_id';
old   2: from v$sql where sql_id = '&sql_id'
new   2: from v$sql where sql_id = '1qqtru155tyz8'

SQL_ID        LOADED_VERSIONS EXECUTIONS OPTIMIZER_ PLAN_HASH_VALUE CHILD_NUMBER CHILD_ADDRESS
------------- --------------- ---------- ---------- --------------- ------------ ----------------
1qqtru155tyz8               1          1 ALL_ROWS        1601196873            0 0000000081111008
1qqtru155tyz8               1          1 ALL_ROWS        2572036781            1 00000000841B1DD8
1qqtru155tyz8               1          1 FIRST_ROWS      1601196873            2 00000000813D1A70
1qqtru155tyz8               1          1 FIRST_ROWS      2572036781            3 000000007FFE3370

--從上面的查詢結果知,optimizer_mode不同,plan_hash_value的值不同,child_address的值也不同
--尤其是child_address表明是pin到shared_pool中不同的位置

--檢視v$sqlarea檢視
SQL> select sql_id,sql_text,version_count vs_cnt,loaded_versions ld_vs,executions ex_cnt
  2  from v$sqlarea where sql_id = '&sql_id';
old   2: from v$sqlarea where sql_id = '&sql_id'
new   2: from v$sqlarea where sql_id = '1qqtru155tyz8'

SQL_ID        SQL_TEXT                                        VS_CNT      LD_VS     EX_CNT
------------- ------------------------------------------- ---------- ---------- ----------
1qqtru155tyz8 SELECT * FROM T LOOK_FOR_ME WHERE X = :B1            4          4          4

--從上面的檢視可知,是sql_id的一個聚合,列出了version_count以及executions的總次數等

--下面來看看每個sql對應的執行計劃
--child_number為0的遊標,此時為父遊標
SQL> select * from table(dbms_xplan.display_cursor('&sql_id',0));
old   1: select * from table(dbms_xplan.display_cursor('&sql_id',0))
new   1: select * from table(dbms_xplan.display_cursor('1qqtru155tyz8',0))

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  1qqtru155tyz8, child number 0
-------------------------------------
SELECT * FROM T LOOK_FOR_ME WHERE X = :B1

Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       | 28616 (100)|          |
|*  1 |  TABLE ACCESS FULL| T    | 10000 |   292K| 28616   (6)| 00:05:44 |
--------------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(TO_NUMBER("X")=:B1)  -->存在謂詞轉換

--下面是child_number為1的子游標
SQL> select * from table(dbms_xplan.display_cursor('&sql_id',1));
old   1: select * from table(dbms_xplan.display_cursor('&sql_id',1))
new   1: select * from table(dbms_xplan.display_cursor('1qqtru155tyz8',1))

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  1qqtru155tyz8, child number 1
-------------------------------------
SELECT * FROM T LOOK_FOR_ME WHERE X = :B1

Plan hash value: 2572036781

--------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |       |       |     2 (100)
|   1 |  TABLE ACCESS BY INDEX ROWID| T            |     1 |    30 |     2   (0)
|*  2 |   INDEX UNIQUE SCAN         | SYS_C0011143 |     1 |       |     1   (0)
--------------------------------------------------------------------------------

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

   2 - access("X"=:B1)   

--從上面的兩個執行計劃中可以看出,因為繫結變數的型別不同,導致了sql語句產生了不同的執行計劃
--且第一個執行計劃中使用了隱式轉換

--下面是child_number為2的子游標的執行計劃
--Author : Robinson
--Blog   : http://blog.csdn.net/robinson_0612

SQL> select * from table(dbms_xplan.display_cursor('&sql_id',2));
old   1: select * from table(dbms_xplan.display_cursor('&sql_id',2))
new   1: select * from table(dbms_xplan.display_cursor('1qqtru155tyz8',2))

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  1qqtru155tyz8, child number 2
-------------------------------------
SELECT * FROM T LOOK_FOR_ME WHERE X = :B1

Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       | 28616 (100)|          |
|*  1 |  TABLE ACCESS FULL| T    | 10000 |   292K| 28616   (6)| 00:05:44 |
--------------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(TO_NUMBER("X")=:B1)

--下面是child_number為3的子游標的執行計劃
SQL> select * from table(dbms_xplan.display_cursor('&sql_id',3));
old   1: select * from table(dbms_xplan.display_cursor('&sql_id',3))
new   1: select * from table(dbms_xplan.display_cursor('1qqtru155tyz8',3))

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  1qqtru155tyz8, child number 3
-------------------------------------
SELECT * FROM T LOOK_FOR_ME WHERE X = :B1

Plan hash value: 2572036781

--------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |       |       |     2 (100)
|   1 |  TABLE ACCESS BY INDEX ROWID| T            |     1 |    30 |     2   (0)
|*  2 |   INDEX UNIQUE SCAN         | SYS_C0011143 |     1 |       |     1   (0)
--------------------------------------------------------------------------------

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

   2 - access("X"=:B1)

--子游標2與子游標3也是使用了不同的執行計劃,這個原因與父遊標0,子游標1的原因相同
--子游標2與父遊標0有相同的執行計劃,從Plan hash value的值可知
--同樣,子游標3與父遊標1也有相同的執行計劃,從Plan hash value的值可知

--產生不同執行計劃的原因
--v$sql_shared_cursor檢視記錄了那些不能共享子游標的記錄並給給出原因,如下查詢
SQL> SELECT child_number,bind_mismatch, optimizer_mode_mismatch 
  2  from v$sql_shared_cursor
  3  where sql_id = '&SQL_ID';
old   3: where sql_id = '&SQL_ID'
new   3: where sql_id = '1qqtru155tyz8'

CHILD_NUMBER B O
------------ - -
           0 N N
           1 Y N
           2 N Y
           3 Y Y

--從上面的查詢結果可知,遊標1與父遊標0是由於繫結變數不匹配而導致了不可共享子游標
--遊標2則是由於不同的執行環境,遊標3則是不同的執行環境與不匹配的繫結變數導致不可共享子游標           

--從上面的測試可以,父遊標相同,子游標不同,執行計劃可能相同,也可能不相同 
--下面的這個查詢也說明了這個問題,得到的是不同的PLAN_HASH_VALUE  
SQL> select a.snap_id, a.sql_id, a.plan_hash_value,to_char(b.begin_interval_time,'yyyy-mm-dd hh24:mi:ss')
  2  from dba_hist_sqlstat a, dba_hist_snapshot b 
  3  where a.snap_id = b.snap_id
  4  and sql_id ='&sql_id';
old   4: and sql_id ='&sql_id'
new   4: and sql_id ='1qqtru155tyz8'

   SNAP_ID SQL_ID        PLAN_HASH_VALUE TO_CHAR(B.BEGIN_INT
---------- ------------- --------------- -------------------
       275 1qqtru155tyz8      1601196873 2013-03-08 12:00:25
       275 1qqtru155tyz8      2572036781 2013-03-08 12:00:25


四、小結
    a、本文討論了v$sql,v$sqlarea,v$sqltext以及v$sqltext_with_newlines幾個檢視的差異
    b、需要記住的是v$sql儲存所有遊標,v$sqlarea等同於使用了distinct關鍵字,僅保留sql語句。v$sqltext提供完整的sql語句
    c、硬解析通常是由於不可共享的父遊標造成的,如經常變動的SQL語句,或動態SQL或未使用繫結變數等
    d、與父遊標SQL文字完全一致的情形下,多個相同的SQL語句可以共享一個父遊標
    e、SQL文字、執行環境完全一致的情形下,子游標能夠被共享,否則如果執行環境不一致則生成新的子游標
    f、父遊標相同,子游標不同,執行計劃可能相同,也可能不相同

六、更多參考: