天天看點

oracle一個事務的完整流程分析

oracle服務程序如何處理使用者程序的請求

伺服器程序在完成使用者程序的請求過程中,主要完成如下7個任務:

0.sql語句的解析

1.資料塊的讀入db buffer

2.記日志

3.為事務建立復原段

4.本事務修改資料塊

5.放入dirty list

6.使用者commit或rollback

下面要講oracle伺服器程序如可處理使用者程序的請求,當一使用者程序送出一個sql時:update temp set a=a*2;首先oracle伺服器程序從使用者程序把資訊接收到後,在PGA中就要此程序配置設定所需記憶體,存儲相關的資訊,如在會話記憶體存儲相關的登入資訊等;伺服器程序把這個sql語句的字元轉化為ASCII等效數字碼,接着這個ASCII碼被傳遞給一個HASH函數,并傳回一個hash值,然後伺服器程序将到shared pool中的library cache中去查找是否存在相同的hash值,如果存在,伺服器程序将使用這條語句已高速緩存在SHARED POOL的library cache中的已分析過的版本來執行,如果不存在,伺服器程序将在CGA中,配合UGA内容對sql,進行文法分析,首先檢查文法的正确性,接着對語句中涉及的表,索引,視圖等對象進行解析,并對照資料字典檢查這些對象的名稱以及相關結構,并根據ORACLE選用的優化模式以及資料字典中是否存在相應對象的統計資料和是否使用了存儲大綱來生成一個執行計劃或從存儲大綱中選用一個執行計劃,然後再用資料字典核對此使用者對相應對象的執行權限,最後生成一個編譯代碼。ORACLE将這條sql語句的本身實際文本、HASH值、編譯代碼、與此語名相關聯的任何統計資料和該語句的執行計劃緩存在SHARED POOL的library cache中。伺服器程序通過SHARED POOL 鎖存器(shared pool latch)來申請可以向哪些共享PL/SQL區中緩存這此内容,也就是說被SHARED POOL鎖存器鎖定的PL/SQL區中的塊不可被覆寫,因為這些塊可能被其它程序所使用。在SQL分析階段将用到LIBRARY CACHE,從資料字典中核對表、視圖等結構的時候,需要将資料字典從磁盤讀入LIBRARY CACHE,是以,在讀入之前也要使用LIBRARY CACHE鎖存器(library cache pin,library cache lock)來申請用于緩存資料字典。

到現在為止,這個sql語句已經被編譯成可執行的代碼了,但還不知道要操作哪些資料,是以伺服器程序還要為這個sql準備預處理資料。

Oracle處理資料,都需要把資料讀取到記憶體中(即db buffer中),首先伺服器程序要判斷所需資料是否在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。

現在資料已經被讀入到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等待事件),已經被寫入redo file的redo log buffer所持有的鎖存器會被釋放,并可被後來的寫入資訊覆寫,redo log buffer是循環使用的。Redo file也是循環使用的,當一個redo file 寫滿後,lgwr程序會自動切換到下一redo file(這個時候可能出現log file switch(checkpoint complete)等待事件)。如果是歸檔模式,歸檔程序還要将前一個寫滿的redo file檔案的内容寫到歸檔日志檔案中(這個時候可能出現log file switch(archiving needed))。

在完成本事務所有相關的redo log buffer之後,伺服器程序開始改寫這個db buffer的塊頭部事務清單并寫入scn,然後copy包含這個塊的頭部事務清單及scn資訊的資料副本放入復原段中,将這時復原段中的資訊稱為資料塊的“前映像“,這個”前映像“用于以後的復原、恢複和一緻性讀。(復原段可以存儲在專門的復原表空間中,這個表空間由一個或多個實體檔案組成,并專用于復原表空間,復原段也可在其它表空間中的資料檔案中開辟。)

準備工作都已經做好了,現在可以改寫db buffer塊的資料内容了,并在塊的頭部寫入復原段的位址。

5. 放入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鎖阻塞)。

到現在為止,資料已經在db buffer或資料檔案中修改完成,但是否要永久寫到數檔案中,要由使用者來決定commit(儲存更改到資料檔案)和rollback(撤銷資料的更改),下面來看看在commit和rollback時,oracle都在做什麼。

使用者執行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寫進資料檔案的改變将通過重做日志檔案中的記錄進行還原,叫做復原,

如果斷電時有一個已送出,但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 把該記錄删除。)

使用者執行rollback

如果使用者rollback,則伺服器程序會根據資料檔案塊和DB BUFFER中塊的頭部的事務清單和SCN以及復原段位址找到復原段中相應的修改前的副本,并且用這些原值來還原目前資料檔案中已修改但未送出的改變。如果有多個“前映像”,伺服器程序會在一個“前映像”的頭部找到“前前映像”的復原段位址,一直找到同一事務下的最早的一個“前映像”為止。一旦發出了COMMIT,使用者就不能rollback,這使得COMMIT後DBWR程序還沒有全部完成的後續動作得到了保障。

到現在為例一個事務已經結束了。

TM鎖:符合lock機制的,用于保護對象的定義不被修改

TX鎖:這個鎖代表一個事務,是行級鎖,用資料塊頭、資料記錄頭的一些字段表示,也是符合lock機制,有resource structure、lock structure、enqueue算法。