14.1SQL語句處理過程
在進行SQL語句處理優化前,需要先熟悉和了解SQL語句的處理過程。
每種類型的語句在執行時都需要如下階段:
第1步: 創建游標。
第2步: 分析語句。
第5步: 綁定變量。
第7步: t運行語句。
第9步: 關閉游標。
如果使用了并行功能,還會包含下面這個階段:
第6步: 并行執行語句。
如果是查詢語句,則需要幾個額外的步驟:
第3步: 描述查詢的結果集。
第4步: 定義查詢的輸出數據。
第8步: 取查詢出來的行。
第1步 創建游標(Create a Cursor) :由程序接口調用創建一個游標(cursor)。任何SQL語句都會創建它,特別在運行DML語句時,都是自動創建游標的,不需要開發人員干預。多數應用中,游標的創建是自動的。然而,在預編譯程序中創建的游標,可能是隱含的,也可能顯式的創建。在存儲過程中也是這樣的。
第2步分析語句(Parse the Statement):在語法分析期間,SQL語句從用戶進程傳送到Oracle,SQL語句經語法分析后,SQL語句本身與分析的信息都被裝入到共享SQL區。在該階段中,可以解決許多類型的錯誤。
語法分析分別執行下列操作:
(1)翻譯SQL語句,驗證它是合法的語句,即書寫是否正確。
(2)實現數據字典的查找,以驗證是否符合表和列的定義。
(3)在所要求的對象上獲取語法分析鎖,使得在語句的語法分析過程中不改變這些對象的定義。驗證為存取所涉及的模式對象所需的權限是否滿足。決定此語句最佳的執行計劃,將它裝入共享SQL區。
以上任何一步出錯誤,都將導致語句報錯,中止執行。
只有在共享池中不存在等價SQL語句的情況下,才對SQL語句作語法分析。在這種情況下,數據庫內核重新為該語句分配新的共享SQL區,并對語句進行語法分析。進行語法分析需要耗費較多的資源,所以要盡量避免進行語法分析,這是優化的技巧之一。
Oracle只對每個SQL語句翻譯一次,在以后再次執行該語句時,只要該語句還在共享SQL區中,就可以避免對該語句重新進行語法分析,也就是此時可以直接使用其對應的執行計劃對數據進行存取。這主要是通過綁定變量(bind variable)實現的,也就是常說的共享SQL,后面會給出共享SQL的概念。
雖然語法分析驗證了SQL語句的正確性,但語法分析只能識別在SQL語句執行之前所能發現的錯誤(如書寫錯誤、權限不足等)。因此,有些錯誤通過語法分析是抓不到的。例如,在數據轉換中的錯誤或在數據中的錯(如企圖在主鍵中插入重復的值)以及死鎖等均是只有在語句執行階段期間才能遇到和報告的錯誤或情況。 查詢與其它類型的SQL語句不同,因為在成功執行后作為結果將返回數據。
第3步 描述查詢結果;描述階段只有在查詢結果的各個列是未知時才需要;例如,當查詢由用戶交互地輸入需要輸出的列名。在這種情況要用描述階段來決定查詢結果的特征(數據類型,長度和名字)。
第4步定義查詢的輸出數據:在查詢的定義階段,指定與查詢出的列值對應的接收變量的位置、大小和數據類型,這樣通過接收變量就可以得到查詢結果。如果必要的話,Oracle會自動實現數據類型的轉換。這是將接收變量的類型與對應的列類型相比較決定的。
第5步: 綁定變量:Oracle知道了SQL語句的意思,但仍沒有足夠的信息用于執行該語句。Oracle 需要得到在語句中列出的所有變量的值。在該例中,Oracle需要得到對列進行限定的值。得到這個值的過程就叫綁定變量(binding variables)。
此過程稱之為將變量值捆綁進來。程序必須指出可以找到該數值的變量名(該變量被稱為捆綁變量,變量名實質上是一個內存地址,相當于指針)。應用的最終用戶可能并沒有發覺他們正在指定捆綁變量,因為Oracle 的程序可能只是簡單地指示他們輸入新的值,其實這一切都在程序中自動做了。
因為你指定了變量名,在你再次執行之前無須重新捆綁變量。你可以改變綁定變量的值,而Oracle在每次執行時,僅僅使用內存地址來查找此值。
如果Oracle 需要實現自動數據類型轉換的話(除非它們是隱含的或缺省的),你還必須對每個值指定數據類型和長度。
第6步:并行執行語句(Parallelize the Statement ):Oracle 可以在DML語句中執行相應并行查詢操作,對某些DDL操作,如創建索引、用子查詢創建表、在分區表上的操作,可以執行并行操作。并行化可導致多個服務器進程為同一個SQL語句工作,使該SQL語句可以快速完成,但是會耗費更多的資源,所以除非很有必要,否則不要使用并行查詢。
第7步執行語句:Oracle擁有所有需要的信息與資源,可以真正運行SQL語句了。如果該語句為SELECT查詢或INSERT語句,則不需要鎖定任何行,因沒有數據需要被改變。如果語句為UPDATE或DELETE語句,則該語句影響的所有行都被鎖定,防止該用戶提交或回滾之前,別的用戶對這些數據進行修改。這保證了數據的一致性。
對于某些語句,可以指定執行的次數,這稱為批處理。指定執行N次,則綁定變量與定義變量被定義為大小為N的數組的開始位置,這種方法可以減少網絡開銷,也是優化的技巧之一。
第8步取出查詢的行:在fetch階段,行數據被取出來,每個后續的存取操作檢索結果集中的下一行數據,直到最后一行被取出來。上面提到過,批量的fetch是優化的技巧之一。
第9步關閉游標,SQL語句處理的最后一個階段就是關閉游標。
14.2SQL解析共享原理
Oracle將執行過的SQL語句存放在內存的共享池(shared buffer pool)中,可以被所有的數據庫用戶共享。當執行一個SQL語句(有時被稱為一個游標)時,如果它和之前的執行過的語句完全相同,Oracle就能很快獲得已經被解析的語句以及最好的執行路徑。這個功能大大地提高了SQL的執行性能并節省了內存的使用。
為了不重復解析相同的SQL語句,在第一次解析之后,Oracle將SQL語句存放在內存中。這塊位于系統全局區域SGA的共享池中的內存可以被所有的數據庫用戶共享。因此,當執行一個SQL語句(有時被稱為一個游標)時,如果它和之前執行過的語句完全相同,Oracle就能很快獲得已經被解析的語句以及最好的執行方案。Oracle的這個功能大大地提高了SQL的執行性能并節省了內存的使用。
可惜的是,Oracle只對簡單的表提供高速緩沖,這個功能并不適用于多表連接查詢。數據庫管理員必須在啟動參數文件中為這個區域設置合適的參數,當這個內存區域越大,就可以保留更多的語句,當然被共享的可能性也就越大了。當向Oracle提交一個SQL語句時,Oracle會首先在這塊內存中查找相同的語句。
要使用內存中共享池的SQL,必須滿足以下條件:當前被執行的語句和共享池中的語句必須完全相同 (包括大小寫、空格、換行等),兩個語句所指的對象必須完全相同 (同義詞與表是不同的對象);兩個SQL語句中必須使用相同的名字的綁定變量。Oracle對兩者采取的是一種嚴格匹配策略。
能夠使用共享的語句必須滿足三個條件:
(1)前被執行的語句和共享池中的語句必須完全相同 (包括大小寫、空格、換行等)。
(2)兩個語句所指的對象必須完全相同 (同義詞與表是不同的對象),即兩條SQL語句操作的數據庫對象必須相同。
(3)語句中必須使用相同命名的綁定變量。如:第一組的兩個SQL語句是相同的,可以共享;而第二組中兩個語句不同,即使在運行時賦予不同的綁定變量以相同的值:
【例14-1】寫兩組綁定變量的語句,分析是否可以共享。
第一組代碼如下:
select pin,name from people where pin = :blk1.pin;
select pin,name from people where pin =:blk1.pin;
由于語句完全相同,所以第一組的兩個SQL語句是相同的,可以使用共享。
第二組代碼如下:
select pin,name from people where pin =:blk1.ot_jnd;
select pin,name from people where pin = :blk1.ov_jnd;
由于綁定變量名稱不同, 不可以使用共享。
第二組語句每執行一次就需要在SHARE POOL 硬解析一次,一百萬用戶就是一百萬次,消耗CPU和內存,如果業務量大,很可能導致宕庫。如果綁定變量,則只需要硬解析一次,重復調用即可。
硬解析是指Oracle在執行目標SQL時,在庫緩存中找不到可以重用的解析樹和執行計劃,而不得不從頭開始解析目標SQL并生成相應的父游標和子游標(Child Cursor)的過程。硬解析,生成執行計劃需要耗用CPU資源,以及SGA資源。
硬解析過程:
語法、語義及權限檢查;
查詢轉換(通過應用各種不同的轉換技巧,會生成語義上等同的新的SQL語句,如count會轉為count(*));
根據統計信息生成執行計劃(找出成本最低的路徑,這一步比較耗時);
將游標信息(執行計劃)保存到庫緩存。
總結:盡可能的避免硬解析,因為硬解析需要更多的CPU資源,閂等。
盡可能的使用綁定變量來避免硬解析。
cursor_sharing參數應權衡利弊,需要考慮使用similar與force帶來的影響。