Oracle中SQL語句執行過程的深度解析

2021-08-26 01:36:51 字數 3115 閱讀 6686

最近專案做完了(也許並不能說做完了),但是出現了乙個致命的障礙,就是效能。現在想想top的作者是講的是多麼的精闢,效能是貫穿在專案需求,專案分析設計,專案編碼和單元測試、整合等各個階段。而不是等專案開發完畢,再來考慮效能問題。但是,事實已是如此。所以,最近先從sql語句開始優化, 為了更好地優化sql,那麼首先自然需要知道oracle中的sql引擎對一條給定的sql語句,他都做了哪些鮮為人知的事情。本文為個人學習總結,僅作學習記錄,其中不妥或者錯誤之處,敬請指出,本人感激不盡!

為了延續top中從cursor開始說明,這裡的cursor並非pl/sql中的cursor,至於這個cursor是什麼,怎麼用中文解釋,暫時還不清楚。但是我想他其實是乙個概念性的東西,代表了sql在不同環境中的稱謂。頑皮點地說,當一條sql語句進入了sql引擎,其就被cursor了。下面看一張top上的cursor的生命週期:

2:parse cursor:這個過程就會讓sql語句和cursor相關聯了。這個步驟就是通常意義的sql解析,最後將執行計畫存入共享區。然後uga中的cursor會用乙個指標指向共享區的乙個可共享的cursor。

3:define output variables:定義輸出變數主要是對於查詢和使用了returning語法的insert,update,和delete語句。

4:bind input variables:如果sql使用了繫結變數,則這裡需要獲取繫結變數的值。

5:execute cursor:執行,但是sql語句真正的執行往往會延遲到下個步驟。

6:fetch cursor:如果sql返回資料,則這裡獲取返回的資料。

7:close cursor:關閉cursor,釋放uga中占用的資源。但是共享區中的cursor並不會釋放。

在上面這個過程中,對於我們關心的sql語句的解析,一筆帶過。下面繼續分析第二個步驟parse cursor。對於sql語句的解析,早就聽說是軟解析和硬解析,下面我們分析下整個解析的過程:

1、整個解析的過程從包含vpd的謂詞開始。如果該sql中含有應用了vpd(virtual private database)的表,則將會首先在sql語句的where中附加上vpd安全策略的謂詞。

2、語法、語義檢查和許可權控制。語法檢查,對sql語句關鍵字的正確性進行檢查,語義檢查主要判斷該sql中是否引用了不存在的物件或者是否違反了相關約束條件;許可權控制

主要是判斷當前操作是否具備相應的操作許可權。

3、從共享區中獲取乙個parent cursor,如果未獲取到,則在共享區中分配記憶體,定義乙個新的parent cursor。這個parent cursor結構的關鍵資訊就是這個sql語句本身。

4、邏輯優化,這個過程應用各種不同的演算法和策略對sql語句進行轉換,生成很多等價的sql。

5、物理優化,首先將上個步驟中各sql語句生成各自的執行計畫,然後應用系統資料字典中的分析統計資訊或者通過動態取樣獲取的統計資訊為每個執行計畫計算出乙個cost,然後選擇最小cost的執行計畫。

6、在共享區中儲存乙個child cursor,這個child cursor和上面parent cursor是關聯的,child cursor中主要儲存執行計畫和執行環境資訊。

通過上面,可以看到,在共享區中有乙個parent cursor和child cursor來儲存乙個sql語句的資訊,通過查詢v$sqlarea,v$sql,v$sqltext等檢視,我們可以獲取sql和cursor的相關資訊。

對於上面的各個過程,我們看到,parent cursor和child cursor是在共享區的,也就是說其可以被共享,嘿嘿,這就是為什麼有軟解析和硬解析了。

軟解析:如果parent cursor和child cursor在共享區中已經存在,則只需要前兩個步驟就行了。這個就是軟解析。

硬解析:如果parent cursor和child cursor不能共享,則需要完整的過程,這個就是硬解析了。

下面通過乙個例子來說明關於共享的機制和需要注意的問題。

首先,對於如下幾個查詢語句

select * from t where n = 1024;

select * from t where n = 1024;

select * from t where n = 1024;

select * from t where n = 1024;

執行上面四條查詢語句,然後,我們檢視v$sqlarea檢視發現,第一條和第四條是相同的,只需要硬解析一次,當第二次執行的時候就不需要硬解析了,所以第一條語句的執行次數是2。這裡就說明sql語句必須完全相同才能共享parent cursor。

select sql_id,sql_text,executions from v$sqlarea where sql_text like '%n=1024%';結果就不貼了。

繫結變數:對於繫結變數,其對於開發來說,或許會增加其**量。從效能的角度來說,其對oltp系統的影響巨大,很多系統的癱瘓,歸根於此。比如,對於如下的查詢語句,select * from t where n = 1024;如果在程式中,我們構造sql語句的時候,使用如下的**:

private string buildsql(int n){

stringbuilder sb = new stringbuilder("select * from t where n = ");

return sb.tostring();

那麼,如果應用程式中這個n假如有10萬個不同的值,如果這些都被執行了,則在共享區就會儲存10萬條共享記錄。因為對於每個不同的值,其sql就是兩條完全不同的sql。

所以,建議在oltp系統當中盡可能使用繫結變數的方式構造sql語句。但是繫結變數,也會造成執行計畫可能並非最優的。比如:

select * from t where c < 1000;

如果這個查詢的結果記錄數為總資料的90%,則執行計畫將會選擇全表掃瞄,而不會走索引。

select * from t where c < 10;

對於這個查詢,優化器肯定會走索引,而不會走全表掃瞄。

但是當使用繫結變數的時候,查詢語句就是select * from t where c < :c ;這樣優化器並不能知道當前查詢的記錄數的情況,所以,在生成執行計畫的時候,可能最後選擇的執行計畫並不是最優的。

關於這個,在9i中好像得到了改善,增加了bind variable peeking,在硬解析的過程中會將繫結變數的值加上,再生成執行計畫。

oracle 查詢最近執行過的 SQL語句

select sql text,last load time from v sql order by last load time desc select sql text,last load time from v sql where last load time is not null and ...

oracle 查詢最近執行過的 SQL語句

select sql text,last load time from v sql order by last load time desc select sql text,last load time from v sql where last load time is not null and ...

oracle 查詢最近執行過的 SQL語句

select sql text,last load time from v sql order by last load time desc select sql text,last load time from v sql where last load time is not null and ...