sql硬解析
『壹』 sql語句執行流程與順序原理解析
SQL語句執行流程與順序原理解析
Oracle語句執行流程
第一步:客戶端把語句發給伺服器端執行
當我們在客戶端執行SQL語句時,客戶端會把這條SQL語句發送給伺服器端,讓伺服器端的進程來處理這語句。也就是說,Oracle 客戶端是不會做任何的操作,他的主要任務就是把客戶端產生的一些SQL語句發送給伺服器端。伺服器進程從用戶進程把信息接收到後, 在PGA 中就要此進程分配所需內存,存儲相關的信息,如:在會話內存存儲相關的登錄信息等。
雖然在客戶端也有一個資料庫進程,但是,這個進程的作用跟伺服器上的進程作用是不相同的,伺服器上的資料庫進程才會對SQL 語句進行相關的處理。不過,有個問題需要說明,就是客戶端的進程跟伺服器的進程是一一對應的。也就是說,在客戶端連接上伺服器後,在客戶端與伺服器端都會形成一個進程,客戶端上的我們叫做客戶端進程,而伺服器上的我們叫做伺服器進程。
第二步:語句解析
當客戶端把SQL語句傳送到伺服器後,伺服器進程會對該語句進行解析。這個解析的工作是在伺服器端所進行的,解析動作又可分為很多小動作。
1)查詢高速緩存(library cache)
伺服器進程在接到客戶端傳送過來的SQL語句時,不會直接去資料庫查詢。伺服器進程把這個SQL語句的字元轉化為ASCII等效數字碼,接著這個ASCII碼被傳遞給一個HASH函數,並返回一個hash值,然後伺服器進程將到shared pool中的library cache(高速緩存)中去查找是否存在相同的hash值。如果存在,伺服器進程將使用這條語句已高速緩存在SHARED POOL的library cache中的已分析過的版本來執行,省去後續的解析工作,這便是軟解析。若調整緩存中不存在,則需要進行後面的步驟,這便是硬解析。硬解析通常是昂貴的操作,大約占整個SQL執行的70%左右的時間,硬解析會生成執行樹,執行計劃,等等。
所以,採用高速數據緩存的話,可以提高SQL 語句的查詢效率。其原因有兩方面:一方面是從內存中讀取數據要比從硬碟中的數據文件中讀取數據效率要高,另一方面也是因為避免語句解析而節省了時間。
不過這里要注意一點,這個數據緩存跟有些客戶端軟體的數據緩存是兩碼事。有些客戶端軟體為了提高查詢效率,會在應用軟體的客戶端設置數據緩存。由於這些數據緩存的存在,可以提高客戶端應用軟體的查詢效率。但是,若其他人在伺服器進行了相關的修改,由於應用軟體數據緩存的存在,導致修改的數據不能及時反映到客戶端上。從這也可以看出,應用軟體的數據緩存跟資料庫伺服器的高速數據緩存不是一碼事。
2)語句合法性檢查(data dict cache)
當在高速緩存中找不到對應的SQL語句時,則伺服器進程就會開始檢查這條語句的合法性。這里主要是對SQL語句的語法進行檢查,看看其是否合乎語法規則。如果伺服器進程認為這條SQL語句不符合語法規則的時候,就會把這個錯誤信息反饋給客戶端。在這個語法檢查的過程中,不會對SQL語句中所包含的表名、列名等等進行檢查,只是檢查語法。
3)語言含義檢查(data dict cache)
若SQL 語句符合語法上的定義的話,則伺服器進程接下去會對語句中涉及的表、索引、視圖等對象進行解析,並對照數據字典檢查這些對象的名稱以及相關結構,看看這些欄位、表、視圖等是否在資料庫中。如果表名與列名不準確的話,則資料庫會就會反饋錯誤信息給客戶端。
所以,有時候我們寫select語句的時候,若語法與表名或者列名同時寫錯的話,則系統是先提示說語法錯誤,等到語法完全正確後再提示說列名或表名錯誤。
4)獲得對象解析鎖(control structer)
當語法、語義都正確後,系統就會對我們需要查詢的對象加鎖。這主要是為了保障數據的一致性,防止我們在查詢的過程中,其他用戶對這個對象的結構發生改變。
5)數據訪問許可權的核對(data dict cache)
當語法、語義通過檢查之後,客戶端還不一定能夠取得數據,伺服器進程還會檢查連接用戶是否有這個數據訪問的許可權。若用戶不具有數據訪問許可權的話,則客戶端就不能夠取得這些數據。要注意的是資料庫伺服器進程先檢查語法與語義,然後才會檢查訪問許可權。
6)確定最佳執行計劃
當語法與語義都沒有問題許可權也匹配,伺服器進程還是不會直接對資料庫文件進行查詢。伺服器進程會根據一定的規則,對這條語句進行優化。在執行計劃開發之前會有一步查詢轉換,如:視圖合並、子查詢解嵌套、謂語前推及物化視圖重寫查詢等。為了確定採用哪個執行計劃,Oracle還需要收集統計信息確定表的訪問聯結方法等,最終確定可能的最低成本的執行計劃。
不過要注意,這個優化是有限的。一般在應用軟體開發的過程中,需要對資料庫的sql語句進行優化,這個優化的作用要大大地大於伺服器進程的自我優化。
當伺服器進程的優化器確定這條查詢語句的最佳執行計劃後, 就會將這條SQL語句與執行計劃保存到數據高速緩存(library cache)。如此,等以後還有這個查詢時,就會省略以上的語法、語義與許可權檢查的步驟,而直接執行SQL語句,提高SQL語句處理效率。
第三步:綁定變數賦值
如果SQL語句中使用了綁定變數,掃描綁定變數的聲明,給綁定變數賦值,將變數值帶入執行計劃。若在解析的第一個步驟,SQL在高速緩沖中存在,則直接跳到該步驟。
第四步:語句執行
語句解析只是對SQL語句的語法進行解析,以確保伺服器能夠知道這條語句到底表達的是什麼意思。等到語句解析完成之後,資料庫伺服器進程才會真正的執行這條SQL語句。
對於SELECT語句:
1)首先伺服器進程要判斷所需數據是否在db buffer存在,如果存在且可用,則直接獲取該數據而不是從資料庫文件中去查詢數據,同時根據LRU 演算法增加其訪問計數;
2)若數據不在緩沖區中,則伺服器進程將從資料庫文件中查詢相關數據,並把這些數據放入到數據緩沖區中(buffer cache)。
其中,若數據存在於db buffer,其可用性檢查方式為:查看db buffer塊的頭部是否有事務,如果有事務,則從回滾段中讀取數據;如果沒有事務,則比較select的scn和db buffer塊頭部的scn,如果前者小於後者,仍然要從回滾段中讀取數據;如果前者大於後者,說明這是一非臟緩存,可以直接讀取這個db buffer塊的中內容。
對於DML語句(insert、delete、update):
1)檢查所需的資料庫是否已經被讀取到緩沖區緩存中。如果已經存在緩沖區緩存,則直接執行步驟3;
2)若所需的資料庫並不在緩沖區緩存中,則伺服器將數據塊從數據文件讀取到緩沖區緩存中;
3)對想要修改的表取得的數據行鎖定(Row Exclusive Lock),之後對所需要修改的數據行取得獨占鎖;
4)將數據的Redo記錄復制到redo log buffer;
5)產生數據修改的undo數據;
6)修改db buffer;
7)dbwr將修改寫入數據文件;
其中,第2步,伺服器將數據從數據文件讀取到db buffer經經歷以下步驟:
1)首先伺服器進程將在表頭部請求TM鎖(保證此事務執行過程其他用戶不能修改表的結構),如果成功加TM鎖,再請求一些行級鎖(TX鎖),如果TM、TX鎖都成功加鎖,那麼才開始從數據文件讀數據。
2)在讀數據之前,要先為讀取的文件准備好buffer空間。伺服器進程需要掃描LRU list尋找free db buffer,掃描的過程中,伺服器進程會把發現的所有已經被修改過的db buffer注冊到dirty list中。如果free db buffer及非臟數據塊緩沖區不足時,會觸發dbwr將dirty buffer中指向的緩沖塊寫入數據文件,並且清洗掉這些緩沖區來騰出空間緩沖新讀入的數據。
3)找到了足夠的空閑buffer,伺服器進程將從數據文件中讀入這些行所在的每一個數據塊(db block)(DB BLOCK是ORACLE的最小操作單元,即使你想要的數據只是DB BLOCK中很多行中的一行或幾行,ORACLE也會把這個DB BLOCK中的所有行都讀入Oracle DB BUFFER中)放入db buffer的空閑的區域或者覆蓋已被擠出LRU list的非臟數據塊緩沖區,並且排列在LRU列表的頭部,也就是在數據塊放入db buffer之前也是要先申請db buffer中的鎖存器,成功加鎖後,才能讀數據到db buffer。
若數據塊已經存在於db buffer cache(有時也稱db buffer或db cache),即使在db buffer中找到一個沒有事務,而且SCN比自己小的非臟緩存數據塊,伺服器進程仍然要到表的頭部對這條記錄申請加鎖,加鎖成功才能進行後續動作,如果不成功,則要等待前面的進程解鎖後才能進行動作(這個時候阻塞是tx鎖阻塞)。
在記redo日誌時,其具體步驟如下:
1)數據被讀入到db buffer後,伺服器進程將該語句所影響的並被讀入db buffer中的這些行數據的rowid及要更新的原值和新值及scn等信息從PGA逐條的寫入redo log buffer中。在寫入redo log buffer之前也要事先請求redo log buffer的鎖存器,成功加鎖後才開始寫入。
2)當寫入達到redo log buffer大小的三分之一或寫入量達到1M或超過三秒後或發生檢查點時或者dbwr之前發生,都會觸發lgwr進程把redo log buffer的數據寫入磁碟上的redo file文件中(這個時候會產生log file sync等待事件)。
3)已經被寫入redo file的redo log buffer所持有的鎖存器會被釋放,並可被後來的寫入信息覆蓋,redo log buffer是循環使用的。Redo file也是循環使用的,當一個redo file寫滿後,lgwr進程會自動切換到下一redo file(這個時候可能出現log file switch(check point complete)等待事件)。如果是歸檔模式,歸檔進程還要將前一個寫滿的redo file文件的內容寫到歸檔日誌文件中(這個時候可能出現log file switch(archiving needed)。
在為事務建立undo信息時,其具體步驟如下:
1)在完成本事務所有相關的redo log buffer之後,伺服器進程開始改寫這個db buffer的塊頭部事務列表並寫入scn(一開始scn是寫在redo log buffer中的,並未寫在db buffer)。
2)然後包含這個塊的頭部事務列表及scn信息的數據副本放入回滾段中,將這時回滾段中的信息稱為數據塊的「前映像」,這個「前映像」用於以後的回滾、恢復和一致性讀。(回滾段可以存儲在專門的回滾表空間中,這個表空間由一個或多個物理文件組成,並專用於回滾表空間,回滾段也可在其它表空間中的數據文件中開辟)。
在修改信息寫入數據文件時,其具體步驟如下:
1)改寫db buffer塊的數據內容,並在塊的頭部寫入回滾段的地址。
2)將db buffer指針放入dirty list。如果一個行數據多次update而未commit,則在回滾段中將會有多個「前映像」,除了第一個「前映像」含有scn信息外,其他每個"前映像"的頭部都有scn信息和"前前映像"回滾段地址。一個update只對應一個scn,然後伺服器進程將在dirty list中建立一條指向此db buffer塊的指針(方便dbwr進程可以找到dirty list的db buffer數據塊並寫入數據文件中)。接著伺服器進程會從數據文件中繼續讀入第二個數據塊,重復前一數據塊的動作,數據塊的讀入、記日誌、建立回滾段、修改數據塊、放入dirty list。
3)當dirty queue的長度達到閥值(一般是25%),伺服器進程將通知dbwr把臟數據寫出,就是釋放db buffer上的鎖存器,騰出更多的free db buffer。前面一直都是在說明oracle一次讀一個數據塊,其實oracle可以一次讀入多個數據塊(db_file_multiblock_read_count來設置一次讀入塊的個數)
當執行commit時,具體步驟如下:
1)commit觸發lgwr進程,但不強制dbwr立即釋放所有相應db buffer塊的鎖。也就是說有可能雖然已經commit了,但在隨後的一段時間內dbwr還在寫這條sql語句所涉及的數據塊。表頭部的行鎖並不在commit之後立即釋放,而是要等dbwr進程完成之後才釋放,這就可能會出現一個用戶請求另一用戶已經commit的資源不成功的現象。
2)從Commit和dbwr進程結束之間的時間很短,如果恰巧在commit之後,dbwr未結束之前斷電,因為commit之後的數據已經屬於數據文件的內容,但這部分文件沒有完全寫入到數據文件中。所以需要前滾。由於commit已經觸發lgwr,這些所有未來得及寫入數據文件的更改會在實例重啟後,由smon進程根據重做日誌文件來前滾,完成之前commit未完成的工作(即把更改寫入數據文件)。
3)如果未commit就斷電了,因為數據已經在db buffer更改了,沒有commit,說明這部分數據不屬於數據文件。由於dbwr之前觸發lgwr也就是只要數據更改,(肯定要先有log)所有dbwr在數據文件上的修改都會被先一步記入重做日誌文件,實例重啟後,SMON進程再根據重做日誌文件來回滾。
其實smon的前滾回滾是根據檢查點來完成的,當一個全部檢查點發生的時候,首先讓LGWR進程將redologbuffer中的所有緩沖(包含未提交的重做信息)寫入重做日誌文件,然後讓dbwr進程將dbbuffer已提交的緩沖寫入數據文件(不強制寫未提交的)。然後更新控制文件和數據文件頭部的SCN,表明當前資料庫是一致的,在相鄰的兩個檢查點之間有很多事務,有提交和未提交的。
當執行rollback時,具體步驟如下:
伺服器進程會根據數據文件塊和db buffer中塊的頭部的事務列表和SCN以及回滾段地址找到回滾段中相應的修改前的副本,並且用這些原值來還原當前數據文件中已修改但未提交的改變。如果有多個」前映像「,伺服器進程會在一個「前映像」的頭部找到「前前映像」的回滾段地址,一直找到同一事務下的最早的一個「前映像」為止。一旦發出了commit,用戶就不能rollback,這使得commit後dbwr進程還沒有全部完成的後續動作得到了保障。
第五步:提取數據
當語句執行完成之後,查詢到的數據還是在伺服器進程中,還沒有被傳送到客戶端的用戶進程。所以,在伺服器端的進程中,有一個專門負責數據提取的一段代碼。他的作用就是把查詢到的數據結果返回給用戶端進程,從而完成整個查詢動作。
從這整個查詢處理過程中,我們在資料庫開發或者應用軟體開發過程中,需要注意以下幾點:
一是要了解資料庫緩存跟應用軟體緩存是兩碼事情。資料庫緩存只有在資料庫伺服器端才存在,在客戶端是不存在的。只有如此,才能夠保證資料庫緩存中的內容跟資料庫文件的內容一致。才能夠根據相關的規則,防止數據臟讀、錯讀的發生。而應用軟體所涉及的數據緩存,由於跟資料庫緩存不是一碼事情,所以,應用軟體的數據緩存雖然可以提高數據的查詢效率,但是,卻打破了數據一致性的要求,有時候會發生臟讀、錯讀等情況的發生。所以,有時候,在應用軟體上有專門一個功能,用來在必要的時候清除數據緩存。不過,這個數據緩存的清除,也只是清除本機上的數據緩存,或者說,只是清除這個應用程序的數據緩存,而不會清除資料庫的數據緩存。
二是絕大部分SQL語句都是按照這個處理過程處理的。我們DBA或者基於Oracle資料庫的開發人員了解這些語句的處理過程,對於我們進行涉及到SQL語句的開發與調試,是非常有幫助的。有時候,掌握這些處理原則,可以減少我們排錯的時間。特別要注意,資料庫是把數據查詢許可權的審查放在語法語義的後面進行檢查的。所以,有時會若光用資料庫的許可權控制原則,可能還不能滿足應用軟體許可權控制的需要。此時,就需要應用軟體的前台設置,實現許可權管理的要求。而且,有時應用資料庫的許可權管理,也有點顯得繁瑣,會增加伺服器處理的工作量。因此,對於記錄、欄位等的查詢許可權控制,大部分程序涉及人員喜歡在應用程序中實現,而不是在資料庫上實現。
Oracle SQL語句執行順序
(8)SELECT (9) DISTINCT (11) <select_list>
(1) FROM <left_table>
(3) <join_type> JOIN <right_table>
(2) ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(6) WITH {CUBE | ROLLUP}
(7) HAVING <having_condition>
(10) ORDER BY <order_by_list>
1)FROM:對FROM子句中的表執行笛卡爾積(交叉聯接),生成虛擬表VT1。
2)ON:對VT1應用ON篩選器,只有那些使為真才被插入到TV2。
3)OUTER (JOIN):如果指定了OUTER JOIN(相對於CROSS JOIN或INNER JOIN),保留表中未找到匹配的行將作為外部行添加到VT2,生成TV3。如果FROM子句包含兩個以上的表,則對上一個聯接生成的結果表和下一個表重復執行步驟1到步驟3,直到處理完所有的表位置。
4)WHERE:對TV3應用WHERE篩選器,只有使為true的行才插入TV4。
5)GROUP BY:按GROUP BY子句中的列列表對TV4中的行進行分組,生成TV5。
6)CUTE|ROLLUP:把超組插入VT5,生成VT6。
7)HAVING:對VT6應用HAVING篩選器,只有使為true的組插入到VT7。
8)SELECT:處理SELECT列表,產生VT8。
9)DISTINCT:將重復的行從VT8中刪除,產品VT9。
10)ORDER BY:將VT9中的行按ORDER BY子句中的列列表順序,生成一個游標(VC10),生成表TV11,並返回給調用者。
以上每個步驟都會產生一個虛擬表,該虛擬表被用作下一個步驟的輸入。這些虛擬表對調用者(客戶端應用程序或者外部查詢)不可用。只有最後一步生成的表才會會給調用者。如果沒有在查詢中指定某一個子句,將跳過相應的步驟。
『貳』 存儲過程EXECUTE IMMEDIATE V_SQL執行很慢或出不來,如果把V_SQL語句拿出來單獨執行很快,這是為什麼
這個可能的原因有很多;
首先,execute immediate v_sql語句需要硬解析,這是一個較為耗時的過程,而直接拿出來執行的語句,其查詢分析樹已經存在,所以只需要軟解析。
另外,execute immediate語句由於是硬解析,所以每次出來的計劃都有可能不同,這是最需要注意的,可以為兩種執行方法設置10046跟蹤,看一下每種方法的耗時點,以及執行計劃的異同。
以下配置可以在plsql developer的命令窗口中執行,也可以在sqlplus中執行;
sql> alter session set events '10046 trace name context forever,level 8';
sql > execute immediate v_sql;
sql> v_sql;
sql> alter session set events '10046 trace name context off';
完成上述操作後登錄到資料庫伺服器上,在cmd(windows)或者 shell(unix)環境下執行
$ tkprof tracefile.trc tracefile.txt sys=no