oracle學習筆記 共享SQL減少硬解析

oracle學習筆記

共享SQL減少硬解析

上節課講了硬解析多了會消耗cpu資源,容易產生4031錯誤
這是我們不希望出現的

如何去減少硬解析呢
一個最好的辦法,最常用的最有效的辦法就是讓sql共享

一)sql語句完全相同
執行一百個sql語句
如果有99個sql語句都是一樣的話
這裏這個相同的語句就發生了98次軟解析而只有1次硬解析

共享sql就是讓sql完全相同

librarycache裏面有好多鏈
鏈上掛了好多chunk,
chunk裏面有sql和sql執行計劃

一個sql語句進入到數據庫,
要對sql語句進行解析
首先看這個sql語句有沒有被解析過
要做下面的工作:
sql語句轉爲ASCII碼值
然後進行hash運算,得到一個hash值,
再進行一次運算,判斷sql語句在哪個鏈上或者應該掛在哪個鏈上。
然後找到相應的鏈,
拿着sql語句的hash值,
在鏈上進行遍歷和該鏈上的每一個chunk進行比較。
找到相同的則此語句被解析過,
若整條鏈從頭到尾都找不到相同的,則此語句沒有被解析過。
查找過程比較的是hash值
hash值是從ascii碼值來的
scii碼值不一樣,hash值就不一樣
hash值不一樣我們認爲兩個sql不一樣就不共享

ascii碼值是根據sql語句裏面的字母計算出的ascii碼值
學過計算機的都知道
每一個字母對應ascii碼值
所以這個地方強調一下
兩個sql語句要共享必須相同
裏面的每一個字母都必須相同
才能說sql語句相同,才能實現共享

如三個sql語句

select /*hello*/ count(*) from t1 where OBJECT_ID=1;

select /*hello*/ count(*) from t1 where OBJECT_ID=2;

select /*hello*/ count(*)  from t1 where OBJECT_ID=1;

第一個sql語句和第二個sql語句不一樣
因爲有兩個字母1 和 2 不一樣
不能共享。

第一個和第三個也不能共享
第三個語句from關鍵字前面比第一個語句多了一個空格
因爲空格也是一個字符
就是說第三個語句比第一個多了一個字符,就是多了一個ascii碼值
顯然又不一樣。

sql要共享的話,語句必須寫的完全一模一樣,
多空格、多回車、大小寫不一樣、裏面的數值不一樣都不行
都不能實現共享。

二)判斷語句有沒有共享

後面的一些舉例結果較長,
爲了閱讀方便需爲sql輸出設置較大的頁長和較寬的頁寬
SQL> set pagesize 1000
SQL> set linesize 100

怎麼來判斷他們不能共享呢
下面執行一下三個語句
SQL> select /*hello*/ count(*) from t1 where OBJECT_ID=1;


  COUNT(*)
----------
         0


SQL> select /*hello*/ count(*) from t1 where OBJECT_ID=2;


  COUNT(*)
----------
         1


SQL> select /*hello*/ count(*)  from t1 where OBJECT_ID=1;


  COUNT(*)
----------
         0


再查一個數據
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
SQL> select sql_id,sql_text,executions from v$sql where sql_text like '%hello%';


SQL_ID
-------------
SQL_TEXT
----------------------------------------------------------------------------------------------------
EXECUTIONS
----------
1vx8fabpy9dwv
select sql_id,sql_text,executions from v$sql where sql_text like '%hello%'
         1


4zj8791gxaafp
select /*hello*/ count(*)  from t1 where OBJECT_ID=1
         1


gwunm630jqjrr
select /*hello*/ count(*) from t1 where OBJECT_ID=2
         1


dkxtdpmw5ztxm
select /*hello*/ count(*) from t1 where OBJECT_ID=1
         1


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


結果中都是下面的形式:
4zj8791gxaafp
select /*hello*/ count(*)  from t1 where OBJECT_ID=1
         1

select /*hello*/ count(*)  from t1 where OBJECT_ID=1
爲執行的sql語句
4zj8791gxaafp
爲此sql語句的編碼
1
爲此sql語句的執行次數

一個sql語句進入後我們對sql語句進行解析時
我們首先給sql語句分配一個sql ID即編碼

我們找到剛纔執行的三條sql語句對應的三個id
分別是
dkxtdpmw5ztxm
gwunm630jqjrr
4zj8791gxaafp
這是本人試驗的結果,和老師得到的結果一樣。

給三個sql語句分配三個ID,
是因爲oracle認爲這三個sql語句不一樣
它們的執行次數也都爲1,沒有共享。

sql語句要共享的話必須完全相同。
空格、大小寫、數值不一樣、回車等等不同
統統認爲是sql語句不一樣。
不一樣也就不能共享、不能共享就會發生硬解析。

三)使用綁定變量

爲了共享sql減少硬解析
1、統一書寫風格 
包括空格、大小寫、回車 
要統一書寫風格會使得一定數量的sql語句能夠共享
2、前兩個sql語句 
一個OBJECT_ID=1
一個OBJECT_ID=2
不管怎麼統一風格都不一樣
針對這種情況
我們使用綁定變量

舉一個綁定變量的例子:
declare  v_sql varchar2(50);
begin  for i in 1..10000 loop
v_sql := 'insert /*hello*/ into test1(id) values (:1)';  
execute immediate v_sql using i;
end loop;  
commit;  
end; 

在例子中有一個sql語句
insert /*hello*/ into test(id) values (:1)

/*hello*/爲註釋
實際執行時沒有任何作用,但可以起到標記這條sql語句的作用。
不過作爲整句的一部分也會使語句ID發生變化

values (:1)部分用了:1
是用的佔位符
執行時要用實值替換,和變量類似但又不同
這裏的:1改寫成:i
整體效果是一樣的都是佔位符

:1部分執行時如果使用變量i的話
需要這樣改兩句
v_sql := 'insert /*hello*/ into test1(id) values (' || i ||')';
execute immediate v_sql;
執行整個語句塊的時候
使用變量這條sql語句會執行
insert /*hello*/ into test(id) values (1)
然後
insert /*hello*/ into test(id) values (2)
以此類推
values i 部分就會在每次語句單獨執行時使用一個常量
每次執行就會不一樣。
就會出現大量的硬解析。

用:1時是佔位符相當於用了變量n1,n2......
執行
execute immediate v_sql using i;

i爲1時將1傳給佔位符但執行時是執行的
insert /*hello*/ into test(id) values (:1)
爲2時仍是執行的
insert /*hello*/ into test(id) values (:1)
這樣每次執行的語句就會相同

我們執行看一下

先清一下內存
SQL> alter system flush shared_pool;


System altered.

本課程前面的部分並沒有創建test表,
這裏要先建立一個test表
SQL> create table test1(id number,name varchar2(8));


Table created.

再執行上面的plsql語句塊
老師提供的語句中要修改10000句太長我改爲了100並不影響效果

SQL> declare  v_sql varchar2(50);
begin  for i in 1..100 loop
v_sql := 'insert /*hello*/ into test1(id) values (:1)';
execute immediate v_sql using i;
end loop;
commit;
end;   2    3    4    5    6    7
  8  /


PL/SQL procedure successfully completed.


plsql語句塊在sqlplus命令行模式下運行方法
1、需要在命令塊後在新行輸入"/"來終止和運行plsql塊命令
2、命令後新行輸入"."可以結束命令,
   這時不會馬上執行
   要緊接着在後續的SQL>後輸入run命令
以上面的命令塊爲例:
SQL> declare  v_sql varchar2(50);
begin  for i in 1..100 loop
v_sql := 'insert /*hello*/ into test1(id) values (:1)';
execute immediate v_sql using i;
end loop;
commit;
end;  2    3    4    5    6    7
  8  .
SQL> run
  1  declare  v_sql varchar2(50);
  2  begin  for i in 1..100 loop
  3  v_sql := 'insert /*hello*/ into test1(id) values (:1)';
  4  execute immediate v_sql using i;
  5  end loop;
  6  commit;
  7* end;


PL/SQL procedure successfully completed.

執行了語句塊後查詢
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
SQL> select sql_id,sql_text,executions from v$sql where sql_text like '%hello%';


SQL_ID
-------------
SQL_TEXT
----------------------------------------------------------------------------------------------------
EXECUTIONS
----------
4t6sqmhr6ctwg
declare  v_sql varchar2(50); begin  for i in 1..100 loop v_sql := 'insert /*hello*/ into test1(id) v
alues (:1)'; execute immediate v_sql using i; end loop; commit; end;
         1


1vx8fabpy9dwv
select sql_id,sql_text,executions from v$sql where sql_text like '%hello%'
         1


3z92h5wnqdxs0
insert /*hello*/ into test1(id) values (:1)
       100


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

結果裏面有
3z92h5wnqdxs0
insert /*hello*/ into test1(id) values (:1)
       100
oracle執行時把:1替換爲實際值
它被執行了100次
通過這個例子看
insert /*hello*/ into test1(id) values (:1)
這條語句實現了綁定變量了,進一步實現了共享sql

這是oracle非常經典的一種使用綁定變量來實現共享sql的一種方法。
在開發時要掌握。

sql語句分爲動態部分和靜態部分

如:
select /*hello*/ count(*) from t1 where OBJECT_ID=1;語句
OBJECT_ID爲列名
t1 爲表名
where等爲關鍵字
其中
select /*hello*/ count(*) from t1 where OBJECT_ID=
爲靜態部分
數值1
爲動態部分

靜態部分內容是有限的
動態部分字面值可以無限制的多

假設這個業務是銀行的查詢業務
一萬個人來查,除了字面值以外前面的值都一樣
不常改變的是靜態部分,字面值部分叫動態部分。
我們要共享sql,
我們在動態部分儘量的使用綁定變量。
進而大大的減少了我們sql語句的數量,
大量的減少了硬解析


四)cursor_sharing參數
oracle還有另外一個參數
cursor_sharing
如果由於各種原因未能在開發初期使用綁定變量
爲了減少硬解析
退而求其次的方法是設置cursor_sharing

SQL> show parameter cursor


NAME                                 TYPE                             VALUE
------------------------------------ -------------------------------- ------------------------------
cursor_sharing                       string                           exact
cursor_space_for_time                boolean                          FALSE
open_cursors                         integer                          300
session_cached_cursors               integer                          150


目前cursor_sharing 
的值是
EXACT

介紹一下在sql命令行下修改某個參數的值方法
如:
SQL> alter system set session_cached_cursors=150 scope=both;
alter system set session_cached_cursors=150 scope=both
                 *
ERROR at line 1:
ORA-02096: specified initialization parameter is not modifiable with this
option

錯誤顯示這個參數是靜態參數,不能使用scope=both參數修改
可以使用:
SQL> alter system set session_cached_cursors=150 scope=spfile;


System altered.

在這裏只是告訴大家,oracle裏面有很多參數,參數是可以修改的。
而某些參數如session_cached_cursors參數
只能靜態改,改完以後數據庫重啓以後纔能有效。
有的參數可以動態改,改完馬上生效。

我們要改一個參數要了解一個參數
oracle官方文檔裏面有,寫的很清晰。

oracle的參數內容在官方文檔的Reference部分
然後在Initialization Parameters中可以找到
CURSOR_SHARING
點擊後得到詳細說明

可看到這個參數有三個值可取
SIMILAR | EXACT | FORCE
並且每個值什麼意思也講的很清晰。

FORCE


Forces statements that may differ in some literals, but are otherwise identical, to share a cursor, unless the literals affect the meaning of the statement.


SIMILAR


Causes statements that may differ in some literals, but are otherwise identical, to share a cursor, unless the literals affect either the meaning of the statement or the degree to which the plan is optimized.


EXACT


Only allows statements with identical text to share the same cursor.

翻譯過來的意思就是:
     1)、EXACT(精確的):exact值是Oracle推薦的,也是默認的,
         它要求SQL語句在完全相同時纔會重用,否則會被重新執行硬解析操作。


     2)、SIMILAR(相似的):similar是在Oracle認爲某條SQL語句的謂詞條件可能會影響到它的執行計劃時,
         纔會被重新分析,否則將重用SQL。


     3)、FORCE(強制):force是在任何情況下,無條件重用SQL。

修改cursor_sharing方法
SQL> alter system set cursor_sharing='force';


System altered.
然後看一下
SQL> show parameter cursor


NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
cursor_sharing                       string      force
cursor_space_for_time                boolean     FALSE
open_cursors                         integer     300
session_cached_cursors               integer     20


修改以後對簡單的sql語句有效果

修改前cursor_sharing的值爲默認EXACT
看看效果
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
SQL> insert /*hello*/ into test1(id) values (1111);


1 row created.


SQL> insert /*hello*/ into test1(id) values (2222);


1 row created.


SQL> insert /*hello*/ into test1(id) values (3333);


1 row created.


SQL> select sql_text,sql_id,executions from v$sql where sql_text like '%hello%';


SQL_TEXT
----------------------------------------------------------------------------------------------------
SQL_ID        EXECUTIONS
------------- ----------
insert /*hello*/ into test1(id) values (1111)
34wszcsuxhxww          1


select sql_text,sql_id,executions from v$sql where sql_text like '%hello%'
5tmdtcpq0p7dr          1


insert /*hello*/ into test1(id) values (2222)
9tgjs7a8w2421          1


insert /*hello*/ into test1(id) values (3333)
8gctdvbahgrv3          1
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

將cursor_sharing修改爲force後
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
SQL> alter system set cursor_sharing='force';


System altered.


SQL> alter system flush shared_pool;


System altered.


SQL> insert /*hello*/ into test1(id) values (1111);


1 row created.


SQL> insert /*hello*/ into test1(id) values (2222);


1 row created.


SQL> insert /*hello*/ into test1(id) values (3333);


1 row created.


SQL> select sql_text,sql_id,executions from v$sql where sql_text like '%hello%';


SQL_TEXT
----------------------------------------------------------------------------------------------------
SQL_ID        EXECUTIONS
------------- ----------
insert /*hello*/ into test1(id) values (:"SYS_B_0")
96g55cvg0b627          3
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
可以看出 values()中的值系統自動替換爲了:"SYS_B_0" 
:"SYS_B_0" 是oracle系統自動起的名字在這裏是佔位符類型
而且EXECUTIONS的次數爲3
說明此語句被共享了。

對cursor_sharing參數修改使用的時間長了你會發現它經常出各種各樣的問題
如cursor_sharing='force'
只對簡單的語句有效,而稍長一點的語句就會沒有反應。
如執行
begin  for i in 1..10000 loop
execute immediate 'insert /*hello*/ into test1(id) values (' || i ||')';
end loop;
commit;
end;
就不會共享i變量

在官方文檔中有這麼一句話:
Oracle does not recommend setting CURSOR_SHARING to FORCE in a DSS environment or if you are using complex queries. Also, star transformation is not supported with CURSOR_SHARING set to either SIMILAR or FORCE. 
說明這個參數還是有一定使用範圍的。
在複雜查詢和DSS(decision support system)決策支持系統環境不建議使用。

而且從網上反應的情況看
cursor_sharing參數的設置是oracle很大的一個報錯點
總之這個地方非常的不好用
而且由它引起的報錯類型很多,
很多人士包括老師都建議儘量不要使用此參數而是在源碼開發時共享變量

原因是oracle使用計算機在語句中自動找到正確的謂詞並使語句正確共享是很困難的事,特別是長語句。

網上也有一些解決方法要使force生效大多要使用到flush shared_pool
這在生產環境非常的不且實際
它刪除了很多原有的執行計劃反而造成了大量的硬解析

最好的辦法是由程序員在開發程序時綁定變量。

五)找出sql語句的共享狀態

如何找出沒有共享的sql語句呢

語句
select SQL_FULLTEXT from v$sql where EXECUTIONS=1 and sql_text like '%from t%';
查執行次數等於一的語句
說明這個sql語句執行過,但執行次數等於一
這個時候還不能完全說明這個sql語句就沒有共享
有可能這個sql語句就執行一次

我們可以使用
select SQL_FULLTEXT from v$sql where EXECUTIONS=1 order by sql_text;
它是將執行次數等於一次的sql語句列出來,同時按照sql語句去排序
如果有很多相鄰的語句非常相似而且都只執行了一次那麼這些語句沒有共享

select SQL_FULLTEXT,EXECUTIONS from v$sql where EXECUTIONS>1 order by sql_text;
可以找執行了2次以上的sql語句,也就是已經共享的語句

六)例子中語句塊變量的使用

首先刪除test表中的所有內容
SQL> delete from test1;
再清內存
SQL> alter system flush shared_pool;
以便查看語句的結果

不使用綁定變量而直接使用變量執行
SQL> declare  v_sql varchar2(50);
begin  for i in 1..100 loop
v_sql := 'insert /*hello*/ into test1(id) values (' || i ||')';
execute immediate v_sql;
end loop;
commit;
end;  2    3    4    5    6    7
  8  /


PL/SQL procedure successfully completed.

或者
SQL> begin  for i in 1..100 loop
execute immediate 'insert /*hello*/ into test1(id) values (' || i ||')';
end loop;
commit;
end;  2    3    4    5
  6  /


PL/SQL procedure successfully completed.
都可以

select sql_text,sql_id,executions from v$sql where sql_text like '%hello%';

結果中都是
SQL_TEXT
----------------------------------------------------------------------------------------------------
SQL_ID        EXECUTIONS
------------- ----------
insert /*hello*/ into test1(id) values (86)
c5j1z1yujs0zg          1


insert /*hello*/ into test1(id) values (99)
d3qwcqfbjn1bk          1


insert /*hello*/ into test1(id) values (67)
這樣的結果。
說明:
insert /*hello*/ into test1(id) values (i)
每個i不同,語句各執行一次
即語句沒有共享。

如果使用下面的方法
SQL> begin  for i in 1..100 loop
insert /*hello*/ into test1(id) values (i);
end loop;
commit;
end;  2    3    4    5
  6  /


PL/SQL procedure successfully completed.


SQL> select sql_text,sql_id,executions from v$sql where sql_text like '%hello%';


SQL_TEXT
----------------------------------------------------------------------------------------------------
SQL_ID        EXECUTIONS
------------- ----------
select sql_text,sql_id,executions from v$sql where sql_text like '%hello%'
5tmdtcpq0p7dr          1


begin  for i in 1..100 loop insert /*hello*/ into test1(id) values (i); end loop; commit; end;
0ba439q9j9f54          1

雖然沒有綁定變量
但是整個plsql語句作爲整體只執行了一次。
並沒有
insert /*hello*/ into test1(id) values (i);語句的單獨執行
和共享變量效果相似

如果把 :1改爲:i
SQL> declare  v_sql varchar2(50);
begin  for i in 1..100 loop
v_sql := 'insert /*hello*/ into test1(id) values (:i)';
execute immediate v_sql using i;
end loop;
commit;
end;  2    3    4    5    6    7
  8  /


PL/SQL procedure successfully completed.


SQL> select sql_id,sql_text,executions from v$sql where sql_text like '%hello%';


SQL_ID
-------------
SQL_TEXT
----------------------------------------------------------------------------------------------------
EXECUTIONS
----------
1vx8fabpy9dwv
select sql_id,sql_text,executions from v$sql where sql_text like '%hello%'
         1


ck6uvahuukch4
declare  v_sql varchar2(50); begin  for i in 1..100 loop v_sql := 'insert /*hello*/ into test1(id) v
alues (:i)'; execute immediate v_sql using i; end loop; commit; end;
         1


dtc5n31qzugsh
insert /*hello*/ into test1(id) values (:i)
       100
:i在這裏仍是佔位符,不是變量
上面是plsql語句塊中values(:1)位置使用變量和佔位符的幾種辦法。


2016年8月27日
文字:韻箏
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章