深刻理解SQL語句執行過程

SQL語句執行過程詳解
一條sql,plsql的執行究竟是怎樣執行的呢?
1、SQL語句執行原理:
第一步:客戶端把語句發給服務器端執行
當咱們在客戶端執行 select 語句時,客戶端會把這條 SQL 語句發送給服務器端,讓服務器端的
進程來處理這語句。也就是說,Oracle 客戶端是不會作任何的操做,他的主要任務就是把客戶端產生
的一些 SQL 語句發送給服務器端。雖然在客戶端也有一個數據庫進程,可是,這個進程的做用跟服務器
上的進程做用事不相同的。服務器上的數據庫進程纔會對SQL 語句進行相關的處理。不過,有個問題需
要說明,就是客戶端的進程跟服務器的進程是一一對應的。也就是說,在客戶端鏈接上服務器後,在客戶
端與服務器端都會造成一個進程,客戶端上的咱們叫作客戶端進程;而服務器上的咱們叫作服務器進程。
第二步:語句解析
當客戶端把 SQL 語句傳送到服務器後,服務器進程會對該語句進行解析。同理,這個解析的工做,
也是在服務器端所進行的。雖然這只是一個解析的動做,可是,其會作不少「小動做」。
1. 查詢高速緩存(library cache)。服務器進程在接到客戶端傳送過來的 SQL 語句時,不
會直接去數據庫查詢。而是會先在數據庫的高速緩存中去查找,是否存在相同語句的執行計劃。若是在
數據高速緩存中,則服務器進程就會直接執行這個 SQL 語句,省去後續的工做。因此,採用高速數據緩
存的話,能夠提升 SQL 語句的查詢效率。一方面是從內存中讀取數據要比從硬盤中的數據文件中讀取
數據效率要高,另外一方面,也是由於這個語句解析的緣由。
不過這裏要注意一點,這個數據緩存跟有些客戶端軟件的數據緩存是兩碼事。有些客戶端軟件爲了
提升查詢效率,會在應用軟件的客戶端設置數據緩存。因爲這些數據緩存的存在,能夠提升客戶端應用軟
件的查詢效率。可是,若其餘人在服務器進行了相關的修改,因爲應用軟件數據緩存的存在,致使修改的
數據不能及時反映到客戶端上。從這也能夠看出,應用軟件的數據緩存跟數據庫服務器的高速數據緩存
不是一碼事。
2. 語句合法性檢查(data dict cache)。當在高速緩存中找不到對應的 SQL 語句時,則服
務器進程就會開始檢查這條語句的合法性。這裏主要是對 SQL 語句的語法進行檢查,看看其是否合乎
語法規則。若是服務器進程認爲這條 SQL 語句不符合語法規則的時候,就會把這個錯誤信息,反饋給客
戶端。在這個語法檢查的過程當中,不會對 SQL 語句中所包含的表名、列名等等進行 SQL 他只是語法
上的檢查。
3. 語言含義檢查(data dict cache)。若 SQL 語句符合語法上的定義的話,則服務器進程
接下去會對語句中的字段、表等內容進行檢查。看看這些字段、表是否在數據庫中。若是表名與列名不
準確的話,則數據庫會就會反饋錯誤信息給客戶端。因此,有時候咱們寫 select 語句的時候,若語法
與表名或者列名同時寫錯的話,則系統是先提示說語法錯誤,等到語法徹底正確後,再提示說列名或表名
錯誤。
4. 得到對象解析鎖(control structer)。當語法、語義都正確後,系統就會對咱們須要查詢
的對象加鎖。這主要是爲了保障數據的一致性,防止咱們在查詢的過程當中,其餘用戶對這個對象的結構發
生改變。
5. 數據訪問權限的核對(data dict cache)。當語法、語義經過檢查以後,客戶端還不必定
可以取得數據。服務器進程還會檢查,你所鏈接的用戶是否有這個數據訪問的權限。若你鏈接上服務器
的用戶不具備數據訪問權限的話,則客戶端就不可以取得這些數據。有時候咱們查詢數據的時候,辛辛苦
苦地把 SQL 語句寫好、編譯經過,可是,最後系統返回個 「沒有權限訪問數據」的錯誤信息,讓咱們氣
半死。這在前端應用軟件開發調試的過程當中,可能會碰到。因此,要注意這個問題,數據庫服務器進程先
檢查語法與語義,而後纔會檢查訪問權限。
6. 肯定最佳執行計劃 ?。當語句與語法都沒有問題,權限也匹配的話,服務器進程仍是不會直接對
數據庫文件進行查詢。服務器進程會根據必定的規則,對這條語句進行優化。不過要注意,這個優化是有
限的。通常在應用軟件開發的過程當中,須要對數據庫的 sql 語言進行優化,這個優化的做用要大大地大
於服務器進程的自我優化。因此,通常在應用軟件開發的時候,數據庫的優化是少不了的。當服務器進程
的優化器肯定這條查詢語句的最佳執行計劃後,就會將這條 SQL 語句與執行計劃保存到數據高速緩存
(library cache)。如此的話,等之後還有這個查詢時,就會省略以上的語法、語義與權限檢查的步驟,
而直接執行 SQL 語句,提升 SQL 語句處理效率。
第三步:語句執行
語句解析只是對 SQL 語句的語法進行解析,以確保服務器可以知道這條語句到底表達的是什麼意
思。等到語句解析完成以後,數據庫服務器進程纔會真正的執行這條 SQL 語句。這個語句執行也分兩
種狀況。
一是若被選擇行所在的數據塊已經被讀取到數據緩衝區的話,則服務器進程會直接把這個數據傳遞
給客戶端,而不是從數據庫文件中去查詢數據。
若數據不在緩衝區中,則服務器進程將從數據庫文件中查詢相關數據,並把這些數據放入到數據緩衝
區中(buffer cache)。
第四步:提取數據
當語句執行完成以後,查詢到的數據仍是在服務器進程中,尚未被傳送到客戶端的用戶進程。因此,
在服務器端的進程中,有一個專門負責數據提取的一段代碼。他的做用就是把查詢到的數據結果返回給
用戶端進程,從而完成整個查詢動做。從這整個查詢處理過程當中,咱們在數據庫開發或者應用軟件開發過
程中,須要注意如下幾點:
一是要了解數據庫緩存跟應用軟件緩存是兩碼事情。數據庫緩存只有在數據庫服務器端才存在,在
客戶端是不存在的。只有如此,纔可以保證數據庫緩存中的內容跟數據庫文件的內容一致。纔可以根據
相關的規則,防止數據髒讀、錯讀的發生。而應用軟件所涉及的數據緩存,因爲跟數據庫緩存不是一碼事
情,因此,應用軟件的數據緩存雖然能夠提升數據的查詢效率,可是,卻打破了數據一致性的要求,有時候
會發生髒讀、錯讀等狀況的發生。因此,有時候,在應用軟件上有專門一個功能,用來在必要的時候清除
數據緩存。不過,這個數據緩存的清除,也只是清除本機上的數據緩存,或者說,只是清除這個應用程序
的數據緩存,而不會清除數據庫的數據緩存。
二是絕大部分 SQL 語句都是按照這個處理過程處理的。咱們 DBA 或者基於 Oracle 數據庫的
開發人員瞭解這些語句的處理過程,對於咱們進行涉及到 SQL 語句的開發與調試,是很是有幫助的。有
時候,掌握這些處理原則,能夠減小咱們排錯的時間。特別要注意,數據庫是把數據查詢權限的審查放在
語法語義的後面進行檢查的。因此,有時會若光用數據庫的權限控制原則,可能還不能知足應用軟件權限
控制的須要。此時,就須要應用軟件的前臺設置,實現權限管理的要求。並且,有時應用數據庫的權限管
理,也有點顯得繁瑣,會增長服務器處理的工做量。所以,對於記錄、字段等的查詢權限控制,大部分程
序涉及人員喜歡在應用程序中實現,而不是在數據庫上實現。
DBCC DROPCLEANBUFFERS
從緩衝池中刪除全部清除緩衝區。
DBCC FREEPROCCACHE
從過程緩存中刪除全部元素。
DBCC FREESYSTEMCACHE
從全部緩存中釋放全部未使用的緩存條目
SQL語句中的函數、關鍵字、排序等執行順序:
1. FROM 子句返回初始結果集。
2. WHERE 子句排除不知足搜索條件的行。
3. GROUP BY 子句將選定的行收集到 GROUP BY 子句中各個惟一值的組中。
4. 選擇列表中指定的聚合函數能夠計算各組的彙總值。
5. 此外,HAVING 子句排除不知足搜索條件的行。
6. 計算全部的表達式;
7. 使用 order by 對結果集進行排序。
8. 查找你要搜索的字段。
2、SQL語句執行完整過程:
1.用戶進程提交一個 sql 語句:
update temp set a=a*2,給服務器進程。
2.服務器進程從用戶進程把信息接收到後,在 PGA 中就要此進程分配所需內存,存儲相關的信息,如在會
話內存存儲相關的登陸信息等。
3.服務器進程把這個 sql 語句的字符轉化爲 ASCII 等效數字碼,接着這個 ASCII 碼被傳遞給一個
HASH 函數,並返回一個 hash 值,而後服務器進程將到shared pool 中的 library cache 中去查找是否存在相
同的 hash 值,若是存在,服務器進程將使用這條語句已高速緩存在 SHARED POOL 的library cache 中的已
分析過的版原本執行。
4.若是不存在,服務器進程將在 CGA 中,配合 UGA 內容對 sql,進行語法分析,首先檢查語法的正確性,接
着對語句中涉及的表,索引,視圖等對象進行解析,並對照數據字典檢查這些對象的名稱以及相關結構,並根據
ORACLE 選用的優化模式以及數據字典中是否存在相應對象的統計數據和是否使用了存儲大綱來生成一個
執行計劃或從存儲大綱中選用一個執行計劃,而後再用數據字典覈對此用戶對相應對象的執行權限,最後生成
一個編譯代碼。
5.ORACLE 將這條 sql 語句的自己實際文本、HASH 值、編譯代碼、與此語名相關聯的任何統計數據
和該語句的執行計劃緩存在 SHARED POOL 的 library cache中。服務器進程經過 SHARED POOL 鎖存
器(shared pool latch)來申請能夠向哪些共享 PL/SQL 區中緩存這此內容,也就是說被SHARED POOL 鎖存
器鎖定的 PL/SQL 區中的塊不可被覆蓋,由於這些塊可能被其它進程所使用。
6.在 SQL 分析階段將用到 LIBRARY
 CACHE,從數據字典中核對錶、視圖等結構的時候,須要將數據
字典從磁盤讀入 LIBRARY
 CACHE,所以,在讀入以前也要使用LIBRARY
 CACHE 鎖存器(library cache
pin,library cache lock)來申請用於緩存數據字典。 到如今爲止,這個 sql 語句已經被編譯成可執行的代碼了,
但還不知道要操做哪些數據,因此服務器進程還要爲這個 sql 準備預處理數據。
7.首先服務器進程要判斷所需數據是否在 db buffer 存在,若是存在且可用,則直接獲取該數據,同時根據
LRU 算法增長其訪問計數;若是 buffer 不存在所需數據,則要從數據文件上讀取首先服務器進程將在表頭部
請求 TM 鎖(保證此事務執行過程其餘用戶不能修改表的結構),若是成功加 TM 鎖,再請求一些行級鎖(TX
鎖),若是 TM、TX 鎖都成功加鎖,那麼纔開始從數據文件讀數據,在讀數據以前,要先爲讀取的文件準備好
buffer 空間。服務器進程須要掃面 LRU list 尋找 free db buffer,掃描的過程當中,服務器進程會把發現的全部
已經被修改過的 db buffer 註冊到 dirty list 中, 這些 dirty buffer 會經過 dbwr 的觸發條件,隨後會被寫出到
數據文件,找到了足夠的空閒 buffer,就能夠把請求的數據行所在的數據塊放入到 db buffer 的空閒區域或者
覆蓋已經被擠出 LRU list 的非髒數據塊緩衝區,並排列在 LRU list 的頭部,也就是在數據塊放入 DB
BUFFER 以前也是要先申請 db buffer 中的鎖存器,成功加鎖後,才能讀數據到 db buffer。
8.記日誌 如今數據已經被讀入到 db buffer 了,如今服務器進程將該語句所影響的並被讀
入 db buffer 中的這些行數據的 rowid 及要更新的原值和新值及 scn 等信息從 PGA 逐條的寫入 redo log
buffer 中。在寫入 redo log buffer 以前也要事先請求 redo log buffer 的鎖存器,成功加鎖後纔開始寫入,當
寫入達到 redo log buffer 大小的三分之一或寫入量達到 1M 或超過三秒後或發生檢查點時或者 dbwr 以前
發生,都會觸發 lgwr 進程把 redo log buffer 的數據寫入磁盤上的 redo file 文件中(這個時候會產生log file
sync 等待事件)
已經被寫入 redofile 的 redo log buffer 所持有的鎖存器會被釋放,並可被後來的寫入信息覆蓋,
redo log buffer是循環使用的。Redo file 也是循環使用的,當一個 redo file 寫滿後,lgwr 進程會自動切換到
下一 redo file(這個時候可能出現 log fileswitch(checkpoint complete)等待事件)。若是是歸檔模式,歸檔進
程還要將前一個寫滿的 redo file 文件的內容寫到歸檔日誌文件中(這個時候可能出現 log file
switch(archiving needed)。
9.爲事務創建回滾段 在完成本事務全部相關的 redo log buffer 以後,服務器進程開始改寫這個 db buffer
的塊頭部事務列表並寫入 scn,而後 copy 包含這個塊的頭部事務列表及 scn 信息的數據副本放入回滾段中,將
這時回滾段中的信息稱爲數據塊的「前映像「,這個」前映像「用於之後的回滾、恢復和一致性讀。(回滾段能夠
存儲在專門的回滾表空間中,這個表空間由一個或多個物理文件組成,並專用於回滾表空間,回滾段也可在其它
表空間中的數據文件中開闢。
10.本事務修改數據塊 準備工做都已經作好了,如今能夠改寫 db buffer 塊的數據內容了,並在塊的頭部寫
入回滾段的地址。
11.放入 dirty list 若是一個行數據屢次 update 而未 commit,則在回滾段中將會有多個「前映像「,除了第
一個」前映像「含有 scn 信息外,其餘每一個「前映像「的頭部都有 scn 信息和「前前映像」回滾段地址。一個
update 只對應一個 scn,而後服務器進程將在 dirty list 中創建一
條指向此 db buffer 塊的指針(方便 dbwr 進程能夠找到 dirty list 的 db buffer 數據塊並寫入數據文件中)。
接着服務器進程會從數據文件中繼續讀入第二個數據塊,重複前一數據塊的動做,數據塊的讀入、記日誌、建
立回滾段、修改數據塊、放入 dirty list。當 dirty queue 的長度達到閥值(通常是 25%),服務器進程將通知
dbwr 把髒數據寫出,就是釋放 db buffer 上的鎖存器,騰出更多的 free db buffer。前面一直都是在說明
oracle 一次讀一個數據塊,其實 oracle 能夠一次讀入多個數據塊(db_file_multiblock_read_count 來設置一
次讀入塊的個數)
說明:
在預處理的數據已經緩存在 db buffer 或剛剛被從數據文件讀入到 db buffer 中,就要根據 sql 語句
的類型來決定接下來如何操做。
1>若是是 select 語句,則要查看 db buffer 塊的頭部是否有事務,若是有事務,則從回滾段中讀取數據;如
果沒有事務,則比較 select 的 scn 和 db buffer 塊頭部的 scn,若是前者小於後者,仍然要從回滾段中讀取數據;
若是前者大於後者,說明這是一非髒緩存,能夠直接讀取這個 db buffer 塊的中內容。
2>若是是 DML 操做,則即便在 db buffer 中找到一個沒有事務,並且 SCN 比本身小的非髒
緩存數據塊,服務器進程仍然要到表的頭部對這條記錄申請加鎖,加鎖成功才能進行後續動做,若是不成功,則要
等待前面的進程解鎖後才能進行動做(這個時候阻塞是 tx 鎖阻塞)。
用戶 commit 或 rollback 到如今爲止,數據已經在 db buffer 或數據文件中修改完
成,可是否要永久寫到數文件中,要由用戶來決定 commit(保存更改到數據文件) rollback 撤銷數據的更改)。
1.用戶執行 commit 命令
只有當 sql 語句所影響的全部行所在的最後一個塊被讀入 db buffer 而且重作信息被寫入 redo log
buffer(僅指日誌緩衝區,而不包括日誌文件)以後,用戶才能夠發去 commit 命令,commit 觸發 lgwr 進程,但不
強制當即 dbwr來釋放全部相應 db buffer 塊的鎖(也就是no-force-at-commit,即提交不強制寫),也就是說有
可能雖然已經 commit 了,但在隨後的一段時間內 dbwr 還在寫這條 sql 語句所涉及的數據塊。表頭部的行鎖
並不在 commit 以後當即釋放,而是要等 dbwr 進程完成以後才釋放,這就可能會出現一個用戶請求另外一用戶
已經 commit 的資源不成功的現象。
A .從 Commit 和 dbwr 進程結束之間的時間很短,若是恰巧在 commit 以後,dbwr 未結束以前斷電,由於
commit 以後的數據已經屬於數據文件的內容,但這部分文件沒有徹底寫入到數據文件中。因此須要前滾。由
於 commit 已經觸發 lgwr,這些全部將來得及寫入數據文件的更改會在實例重啓後,由 smon 進程根據重作日
志文件來前滾,完成以前 commit 未完成的工做(即把更改寫入數據文件)。
B.若是未 commit 就斷電了,由於數據已經在 db buffer 更改了,沒有 commit,說明這部分數據不屬於數
據文件,因爲 dbwr 以前觸發 lgwr 也就是隻要數據更改,(確定要先有 log) 全部 DBWR,在數據文件上的修改
都會被先一步記入重作日誌文件,實例重啓後,SMON 進程再根據重作日誌文件來回滾。
其實 smon 的前滾回滾是根據檢查點來完成的,當一個所有檢查點發生的時候,首先讓 LGWR 進程將
redo log buffer 中的全部緩衝(包含未提交的重作信息)寫入重作日誌文件,而後讓 dbwr 進程將 db buffer 已
提交的緩衝寫入數據文件(不強制寫未提交的)。而後更新控制文件和數據文件頭部的 SCN,代表當前數據庫
是一致的,在相鄰的兩個檢查點之間有不少事務,有提交和未提交的。
像前面的前滾回滾比較完整的說法是以下的說明:

A.發生檢查點以前斷電,而且當時有一個未提交的改變正在進行,實例重啓以後,SMON 進程將從上一個
檢查點開始覈對這個檢查點以後記錄在重作日誌文件中已提交的和未提交改變,由於
dbwr 以前會觸發 lgwr,因此 dbwr 對數據文件的修改必定會被先記錄在重作日誌文件中。所以,斷電前被
DBWN 寫進數據文件的改變將經過重作日誌文件中的記錄進行還原,叫作回滾,
B. 若是斷電時有一個已提交,但 dbwr 動做尚未徹底完成的改變存在,由於已經提交,提交會觸發 lgwr
進程,因此無論 dbwr 動做是否已完成,該語句將要影響的行及其產生的結果必定已經記錄在重作日誌文件中
了,則實例重啓後,SMON 進程根據重作日誌文件進行前滾.
實例失敗後用於恢復的時間由兩個檢查點之間的間隔大小來決定,能夠通個四個參數設置檢查點執行的頻
率:

Log_checkpoint_interval:
決定兩個檢查點之間寫入重作日誌文件的系統物理塊(redo blocks)
的大小,默認值是 0,無限制。
log_checkpoint_timeout:
 兩 個 檢 查 點 之 間 的 時 間 長 度(秒)默 認 值 1800s。
fast_start_io_target:
決定了用於恢復時須要處理的塊的多少,默認值是 0,無限制。
fast_start_mttr_target:
直接決定了用於恢復的時間的長短,默認值是 0,無限制(SMON 進程執行的前滾
和回滾與用戶的回滾是不一樣的,SMON 是根據重作日誌文件進行前滾或回滾,而用戶的回滾必定是根據回滾段
的內容進行回滾的。
在這裏要說一下回滾段存儲的數據,假如是 delete 操做,則回滾段將會記錄整個行的數據,假如是 update,
則回滾段只記錄被修改了的字段的變化前的數據(前映像),也就是沒有被修改的字段是不會被記錄的,假如是
insert,則回滾段只記錄插入記錄的 rowid。 這樣假如事務提交,那回滾段中簡單標記該事務已經提交;假如是
回退,則若是操做是 delete,回退的時候把回滾段中數據從新寫回數據塊,操做若是是 update,則把變化前數據
修改回去,操做若是是 insert,則根據記錄的 rowid 把該記錄刪除。
2.若是用戶 rollback。
則服務器進程會根據數據文件塊和 DB BUFFER 中塊的頭部的事務列表和 SCN 以及回滾段地址找到
回滾段中相應的修改前的副本,而且用這些原值來還原當前數據文件中已修改但未提交的改變。若是有多個
「前映像」,服務器進程會在一個「前映像」的頭部找到「前前映像」的回滾段地址,一直找到同一事務下的最先的
一個「前映像」爲止。一旦發出了 COMMIT,用戶就不能rollback,這使得 COMMIT 後 DBWR 進程尚未
所有完成的後續動做獲得了保障。到如今爲例一個事務已經結束了。
說明:
 TM 鎖:
符合 lock 機制的,用於保護對象的定義不被修改。 TX 鎖:
這個鎖表明一個事務,是行
級鎖,用數據塊頭、數據記錄頭的一些字段表示,也是符合 lock 機制,有 resource structure、lock
structure、enqueue 算法。
前端

相關文章
相關標籤/搜索