Oracle數據庫是如何執行SQL的

1、參考資料

oracle concepts ->Overview of SQL Processing 
SQL Parsing Flow Diagram (Doc ID 32895.1)
Overview of SQL Statement Processing Phases (Doc ID 199273.1)
《Oracle編程藝術 深入理解數據庫體系結構》
《基於Oracle的SQL優化》

2、Oracle SQL執行過程

2.1、SQL語句的執行過程

2.1.1、全局角度來看SQL請求的執行過程

從用戶端發送一條請求,請求中需要處理數據,發送SQL語句給數據庫去執行示意圖如下:
在這裏插入圖片描述

1. 用戶發送一個服務請求,應用通過線程池(充分合理的協調利用cpu 、內存、網絡、i/o等系統資源)來高效的利用資源,當處理的請求中需要訪問數據庫時,執行SQL語句的請求就要從數據庫連接池get connection可以快速和數據庫建立連接,節省了連接數據庫的時間。因爲連接數據庫比較慢,所以有必要提前按照連接池配置的大小來初始化連接。

2. 連接池初始化過程中是如何與數據庫建立聯繫?

連接池發出連接請求,ojdbc驅動通過IP+端口+服務名與遠程數據庫連接,數據庫主機上處理網絡請求的是監聽程序。
監聽器是數據庫服務器端的“門衛”,監聽器註冊了數據庫實例信息,當它接收到請求時,會用自己的配置文件檢查這個請求,如果服務名不對或者IP地址是受限的,則不允許連接數據庫。

Oracle處理連接請求有兩種方式:

1)、專用服務模式(dedicated server) 目前生產使用,也是最常用方式
如果通過直連方式建立專用服務器連接,監聽器就會爲我們創建一個專用服務器進程,就是上圖中看到的Server Process。在linux中是通過fork()系統調用方法來創建這個新的專用服務器進程,所以看到的父進程(PPID)都是1。這個新的進程“繼承”了監聽器建立的連接,此時就與數據庫“物理”地連上了。示意圖如下:
在這裏插入圖片描述

在這種模式下,對於每一個客戶端(或應用的數據庫連接池)會話都會新建一個專用的服務器進程,會話與專用服務器進程是一對一映射關係,這個服務器進程用來接收和執行SQL、讀取數據或者在數據庫緩存中查找想要的數據等。

2)、共享服務模式方式

請求連接共享服務器時,監聽器的行爲和專用服務器不一樣,實際上監聽器進程知道實例中運行了哪些調度程序,監聽接收到連接共享服務器請求後,它會從調度程序池中選一個調度程序進程。監聽器向客戶連接返回如何與調度程序進程連接的信息,做了一次連接請求“轉發”,把連接請求轉發給調度進程後,監聽器的工作就完成了。客戶連接接下來與監聽器斷開連接,並與調度進程直接連接,最後客戶連接與數據庫建立了連接。這個過程如下圖所示:

在這裏插入圖片描述

相比於專用服務器模式,服務器不會爲每個用戶連接創建新的進程。Oracle使用一個“共享進程池”爲多個用戶提供連接服務,共享服務器實際上就是一種連接池機制,和應用端的數據庫連接池目的是一樣的。所有的用戶連接會話共享池子中的連接進程。
與專用服務器大的區別在於:客戶進程不與共享服務器直接通信,專用服務進程會與專用服務器直接通信,因爲是共享的,不能建立一對一的專有機制。所以,oracle使用了一組調度程序(dispatcher或叫分派程序)進程。客戶進程通過網絡與一個調度器進程進行通信,調度程序進程將客戶的請求放入SGA的請求隊列;第一個空閒共享服務器會得到這個請求並進行處理;當完成SQL執行命令後,共享服務器會把相應放在原調度程序(即接收請求的調度程序)的響應隊列中。期間,調度程序進程一直在監視這個隊列,當它發現有結果後,就會把結果回傳給客戶進程。如下示意圖:

在這裏插入圖片描述

調度過程說明: 調度程序首先將請求放在SGA的請求隊列中①。空閒可用的共享服務連接進程從請求隊列中取出②並處理。共享服務器處理結束後,再把響應(返回碼、數據等)放到響應隊列中③,接下來調度程序拿到這個響應④,回傳給客戶連接。

3. 服務器進程接收到SQL語句請求後,在數據庫中進行一系列解析工作,最終生成SQL執行計劃,這一步驟有很多優化點,後面詳細分解。

4. 執行SQL語句階段

1)查詢語句,服務器進程從實際數據文件(表)或數據庫緩衝區高速緩存中存儲的值中檢索任何必需的數據值。

2)DML語句,服務器進程修改 SGA(database buffer cache) 中的數據。因爲提交了事務處理,所以日誌寫進程 (LGWR) 會立即將該事務處理記錄到重做日誌(redo log)文件中。數據庫寫進程 (DBWn)按照觸發條件將修改後的數據塊永久刷入磁盤。

5. 如果事務處理成功,服務器進程將通過網絡嚮應用程序發送一條消息。如果事務處理不成功,則傳送一條錯誤消息。

6. 在整個SQL處理過程中,其它後臺進程同時在運行,用於監視是否有需要干預的情況。比如,某用戶對應的server process出現了異常,後臺pmon進程對該服務器進程進行清理和回收。

2.1.2、數據庫端處理SQL具體過程

現在具體來說說sql在數據庫端處理流程。按照oracle官方的說法,SQL語句的執行有以下步驟:

編號 步驟 說明
1 Syntactic 語法檢查
2 Semantic 確認所有對象都存在並且可以訪問
3 View Merging 進行查詢重寫優化
4 Statement Transformation 將複雜查詢分解
5 Optimization 確定訪問方式,選擇優化策略
6 QEP Generation 形成執行計劃。QEP(query execution plan)
7 QEP Execution 運行執行計劃

上面寫的7個步驟中,前面6個步驟就是通常說的解析(parsing),第7個步驟就是通常說的SQL執行(execution)。同樣來自oracle官方文檔Overview of SQL Statement Processing Phases (Doc ID 199273.1),詳細的描述了每個過程中數據庫都在做什麼,如下圖:

在這裏插入圖片描述

2.2、Oracle對解析過程的優化

2.2.1、減少硬解析 – 軟解析(Soft parse)

什麼是硬解析? 硬解析(Hard parse) 即把整個SQL語句的執行需要完完全全的解析,生成執行計劃。
而硬解析的代價挺高的,生成執行計劃需要耗用CPU資源,以及共享內存(主要是SGA中shared pool區域)資源。shard pool的一個組件庫緩存(libary cache) 使用閂(latch)和mutex來保護共享內存結構。閂是鎖的細化,是一種輕量級的串行化設備,有鎖池和排隊機制。當進程申請到閂後,則這些閂用於保護共享內存的數在同一時刻不會被兩個以上的進程修改。在硬解析時,需要申請閂的使用(shared pool latch),大量的閂的使用由此造成需要使用閂的進程排隊越頻繁,性能則逾低下。具體更深入的細節,Oracle官方文檔並未透漏太多,這裏也不做細講,有興趣的參考網絡上的文章以及流露出來的DSI文檔。

由於硬解析對數據庫性能損耗比較嚴重,所以oracle就把執行過的sql的執行計劃緩存到共享池(shared pool)裏面,當遇到重複執行的SQL從共享池裏面匹配到執行計劃後,直接去執行即可,跳過完整解析的很多步驟,從而提高SQL運行效率,從共享池中匹配到執行計劃,直接去執行sql的過程叫軟解析(Soft parse),如下圖所示:

在這裏插入圖片描述

當應用程序發出SQL語句時,應用程序對應的服務器進程對數據庫進行解析調用(parse call),以準備執行該語句。解析調用打開或創建遊標。
遊標(cursor) 可以簡單理解爲是執行sql的載體。具體是特定於會話的私有SQL區域的句柄,該區域保存已解析的SQL語句和其他處理信息。遊標和私有SQL區域在PGA中。如下圖所示:

在這裏插入圖片描述

接着上圖,具體是如何做shared pool check最後命中libary cache中的緩存的對象(執行計劃):

在這裏插入圖片描述

上面兩個扣來的圖可以看出,整個庫緩存可以看做是由一組Hash Bucket組成,每一個Hash Bucket都對應不同的哈希值,對於單個Hash Bucket而言,裏面存儲的就是哈希值相同的所有庫緩存對象句柄,同一個Bucket中不同的庫緩存對象句柄(父遊標句柄)之間用指針連接起來,組成了一個庫緩存對象句柄鏈表(Library Cache Object Handles。
圖例中,當oracle執行目標sql "select * from emp"時,首先對該sql的sql文本進行hash運算,然後根據計算的hash值去相關Hash Bucket中遍歷對應的庫緩存對象句柄鏈表,如果找到了對應的庫緩存對象句柄(父遊標句柄),接着繼續通過父遊標的heap 0中相關信息,遍歷子游標句柄,最終找到子游標中該SQL的執行計劃、解析樹等對象;如果找不到對應的庫緩存對象句柄,或者找到父遊標但對應的子游標沒有,則意味着要做硬解析的工作,需要從頭把硬解析後的執行計劃、解析樹等對象鏈接在相關的Hash Bucket中的庫緩存對象句柄鏈接表中。

總結來看: 硬解析和軟解析重要的一個區別是shared pool check(檢查組件libary cache)是否能找到緩存的執行計劃相關信息

2.2.2、進一步減少軟解析 – 軟軟解析(Soft soft parse)

軟解析會搜索shared pool中library cache 過多的軟解析仍然可能會導致系統問題,特別是如果有少量的SQL高併發地進行軟解析,會產生library cache latch或者是share方式的mutex爭用,比如數據庫中出現大量的library cache相關等待事件。看oracle是如何優化這一點的

shared cursor概念:

瞭解了上面講的庫緩存結構後,shared cursor就是指緩存在庫緩存裏的一種庫緩存對象,也是一種庫緩存對象之一。shared cursor存儲了目標sql的sql文本、解析樹、該sql所涉及的對象定義、該sql所使用的綁定變量類型和長度、執行計劃等信息。簡單點說cursor就是爲了執行sql的,是執行sql的載體。

session cursor概念:

它是當前session解析和執行sql的載體,屬於客戶端所屬會話私有的,存儲在PGA裏面,session cursor與session是一一對應的,不同session的session cursor之間不共享,而shared cursor是所有會話可以共享的。

session cursor中存儲的是庫緩存中對象句柄的地址、sql文本hash等信息,並沒有像shared cursor一樣存儲sql文本、執行計劃等信息,就是個指向要訪問具體對象句柄的鏈接,如果緩存了session cursor就可以直接訪問到執行計劃,省去了通過層層搜索庫緩存的很多步驟。

客戶端sql執行時必須要有一個session cursor,並且只能對應一個shared cursor,前面講過 一個shared cursor卻可以同時對應多個session cursor。

session cursor 會經歷Open、Parse、Bind、Execute、Fetch、Close中的一個或多個階段。

什麼是軟軟解析:

如果數據庫參數session_cached_cursor值大於0,當滿足一定額外條件(session cursor對應的SQL解析和執行次數超過3次),雖然sql執行完畢客戶端發送了close cursor命令,但oracle不會對session cursor完全執行close操作,將其標記爲soft closed,同時將其緩存到當前session PGA中,當目標sql再次被重複執行時shared cursor和session cursor中都能匹配到記錄,意味着oracle執行sql時節省了open一個新cursor所消耗的資源和時間,colse cursor也不需要做了(soft close),只需要做Parse、Bind、Execute、Fetch; 並且在Parse期間根據緩存在session cursor中的句柄鏈接地址,也就是子游標堆6的DS(堆描述符),直接訪問到執行計劃對象句柄,省去了不少搜索庫緩存句柄。
試想,如果某個父遊標下子游標數量很多,搜索整個鏈表的時間過長,執行sql頻次又很高,那麼軟軟解析的優勢就會體現出來。

子游標堆6的DS(堆描述符):
在這裏插入圖片描述

遊標共享過程和對應解析類型:

無論是硬解析、軟解析還是軟軟解析,oracle在解析和執行目標sql時,始終會去匹配當前session的session_cached_cursor緩存。

硬解析情況: 如果在當前session的pga中找不到匹配的session cursor,oracle就會去庫緩存中找是否存在匹配的Parent Cursor。如果找不到,oracle就會新生成一個session cursor和一對shared cursor(parent cursor和child cursor);如果找到了匹配的parent cursor,但找不到匹配的child cursor,oracle就會新生成一個session cursor和一個child cursor(掛載匹配到的parent cursor)。以上這兩個過程對應的都是硬解析。

軟解析情況: 如果在當前session的pga中找不到匹配的緩存session cursor,但在庫緩存中找到了匹配的parent cursor和child cursor,則oracle會新生成一個session cursor並重用剛剛找到的匹配parent cursor和child cursor,這個過程對應的就是軟解析。

軟解析(軟軟解析)情況: 如果在當前session的pga中找到了匹配的緩存session cursor,此事就不再需要重新生成一個session cursor,並且也不再需要像軟解析那樣去庫緩存中層層查找匹配到parent cursor、child cursor,因爲oracle此時可以重用找到的匹配session cursor,並且可以通過這個session cursor中記錄的對象句柄地址直接訪問到執行該sql所需的對象(包括執行計劃句柄等),這個過程就是軟軟解析。

2.2.3、一次解析,多次執行 – 客戶端語句緩存(statement cache)

前面講的軟軟解析過程中,在session_cached_cursor做了一次查找命中,數據庫把軟軟解析算作一次解析,seeion cursor做了軟關閉並額外記錄些信息。數據庫做這些事情還是會有很少量的性能損耗,如果sql執行量非常大的話,這些累計的性能損耗就不得不重視了。

數據庫上連接的應用可能有好幾百個,如果讓客戶端應用來做類似session_cached_cursor做的事情,就可以在提升數據庫性能的前提下減輕數據庫負載。

客戶端statement cache緩存遊標,可以實現當執行close cursro命令時跳過關閉命令,不會將關閉命令發送給oracel去執行,而前面講的數據庫端session_cached_cursor是將遊標soft close並記錄額外的信息方便快速打開並複用。由於cursor在客戶端程序發出關閉命令後,跳過了關閉,並沒有真的關閉,所以客戶端緩存對應的session cursor一直處於打開狀態,從而節省了open cursor、parse、close cursor這些步驟。

以下是Druid(德魯伊)開啓語句緩存解析次數和執行次數前後對比:
在這裏插入圖片描述

在同樣的應用壓測場景下,應用響應時間以及TPS有小幅度提升,如果SQL執行量特別大的時候,還是可以考慮該功能的。

2.2.4、最高境界,不執行sql – SQL結果集緩存

對於某些表數據不頻繁變更的靜態查詢類SQL,如果把相同sql查詢的結果緩存起來,就不需要去執行sql了,更沒有sql語句解析一說。Oracle提供了服務器端result cache和OCI客戶端查詢緩存兩種方式。

服務器端result cache:

由數據庫參數RESULT_CACHE_MODE(默認值MANUAL)、RESULT_CACHE_MAX_SIZE(結果集緩存總大小)控制。默認情況下查詢語句指定了/+RESULT_CACHE/優化器提示,那麼結果被緩存。

應用場景:生產系統中不會開啓該參數值爲auto,會引起性能問題,以及不可預知的bug問題。所以服務器端結果集緩存功能並不適用。

OCI客戶端查詢緩存:
關於oracle OCI接口介紹參考:https://www.oracle.com/cn/database/technologies/appdev/oci.html

在使用oracle調用接口(oci)客戶端的應用中可以將經常執行查詢的結果集緩存在本地。爲了利用客戶端緩存,必須在數據庫中設置參數:

client_result_cache_size(默認值0):

所有oci客戶端進程獲取最大的緩存,它可以被客戶端配置文件或sqlnet.ora配置參數oci_result_cache_size所覆蓋。

client_result_cache_lag(默認3000毫秒):

指定自從上一次往返於服務器以來的最長時間,OCI客戶端查詢需要在此時間之前往返於服務器,以獲取與客戶端緩存的查詢相關的任何數據庫變化。

應用場景:生產環境的應用中使用oci方式比較少,大部分使用jdbc方式連接數據庫。所以,oci客戶端查詢緩存不常用。

sql結果集緩存最佳實踐:

使用以redis爲代表的內存數據庫,由代碼去靈活控制結果緩存策略,比如:當某表上其中一行數據發生更改後,redis中相應的key就可以置爲失效。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章