在Oracle數據庫中,undo主要有三大做用:提供一致性讀(Consistent Read)、回滾事務(Rollback Transaction)以及實例恢復(Instance Recovery)。數據庫
一致性讀是相對於髒讀(Dirty Read)而言的。假設某個表T中有10000條記錄,獲取全部記錄須要15分鐘時間。當前時間爲9點整,某用戶A發出一條查詢語句:select * from T,該語句在9點15分時執行完畢。當用戶A執行該SQL語句到9點10分的時候,另一個用戶B發出了一條delete命令,將T表中的最後一條記錄刪除並提交了。那麼到9點15分時,A用戶將返回多少條記錄?服務器
若是返回9999條記錄,則說明發生了髒讀;若是仍然返回10000條記錄,則說明發生了一致性讀。很明顯,在 9點鐘那個時間點發出查詢語句時,表T中確實有10000條記錄,只不過因爲I/O的相對較慢,因此纔會花15分鐘完成全部記錄的檢索。對於Oracle 數據庫來講,沒有辦法實現髒讀,必須提供一致性讀,而且該一致性讀是在沒有阻塞用戶的DML的前提下實現的。oracle
那麼undo數據是如何實現一致性讀的呢?仍是針對上面的例子。用戶A在9點發出查詢語句時,服務器進程會將9 點那個時間點上的SCN號記錄下來,假設該SCN號爲SCN9.00。那麼9點整的時刻的SCN9.00必定大於等於記錄在全部數據塊頭部的ITL槽中的 SCN號(若是有多個ITL槽,則爲其中最大的那個SCN號)。ide
注: ITL(Interested Transaction List)是Oracle數據塊內部的一個組成部分,用來記錄該塊全部發生的事務,一個itl能夠看做是一個記錄,在一個時間,能夠記錄一個事務(包括提交或者未提交事務)。固然,若是這個事務已經提交,那麼這個itl的位置就能夠被反覆使用了,由於itl相似記錄,因此,有的時候也叫itl槽位。rest
服務器進程在掃描表T的數據塊時,會把掃描到的數據塊頭部的ITL槽中的SCN號與SCN9:00之間進行比較,哪一個更大。若是數據塊頭部的SCN號比SCN9.00要小,則說明該數據塊在9點之後沒有被更新,能夠直接讀取其中的數據;不然,若是數據塊ITL槽的SCN號比SCN9.00要大,則說明該數據塊在9點之後被更新了,該塊裏的數據已經不是9點那個時間點的數據了,因而要藉助undo塊。視頻
9點10分,B用戶更新了表T的最後一條記錄並提交(注意,在這裏,提交或者不提交併非關鍵,只要用戶B更新了表T,用戶A就會去讀undo數據塊)。假設被更新記錄屬於N號數據塊。那麼這個時候N號數據塊頭部的ITL槽的SCN號就被改成SCN9.10。當服務器進程掃描到被更新的數據塊(也就是N號塊)時,發現其ITL槽中的SCN9.10大於發出查詢時的SCN9.00,說明該數據塊在9點之後被更新了。因而服務器進程到N號塊的頭部,找到SCN9.10所在的ITL槽。因爲ITL槽中記錄了對應的undo塊的地址,因而根據該地址找到undo塊,將 undo塊中的被修改前的數據取出,再結合N號塊裏的數據行,從而構建出9點10分被更新以前的那個時間點的數據塊內容,這樣的數據塊叫作CR塊(Consistent Read)。對於delete來講,其undo信息就是insert,也就是說該構建出來的CR塊中就插入了被刪除的那條記錄。隨後,服務器進程掃描該 CR塊,從而返回正確的10000條記錄。教程
讓咱們繼續把問題複雜化。假設在9點10分B用戶刪除了最後一條記錄並提交之後,緊跟着9點11分,C用戶在同一個數據塊裏(也就是N號塊)插入了2條記錄。這個時候Oracle又是如何實現一致性讀的呢(假設表T的initrans爲1,也就是隻有一個ITL 槽)?由於咱們已經知道,事務須要使用ITL槽,只要該事務提交或回滾,該ITL槽就可以被重用。換句話說,該ITL槽裏記錄的已是SCN9.11,而不是SCN9.10了。這時,ITL槽被覆蓋了,Oracle的服務器進程又怎能找回最初的數據呢?進程
其中的祕密就在於,Oracle在記錄undo數據的時候,不只記錄了改變前的數據,還記錄了改變前的數據所在的數據塊頭部的ITL信息。所以,9點10分B用戶刪除記錄時(位於N號塊裏,並假設該N號塊的ITL信息爲[Undo_block0 / SCN8.50]),則Oracle會將改變前的數據(也就是insert)放到undo塊(假設該undo塊地址爲Undo_block1)裏,同時在該undo塊裏記錄刪除前ITL槽的信息(也就是[Undo_block0 / SCN8.50])。刪除記錄之後,該N號塊的ITL信息變爲 [Undo_block1 / SCN9.10];到了9點11分,C用戶又在N號塊裏插入了兩條記錄,則Oracle將插入前的數據(也就是delete兩條記錄)放到undo塊(假設該undo塊的地址爲Undo_block2)裏,並將9點11分時的ITL槽的信息(也就是[Undo_block1 / SCN9.10])也記錄到該undo塊裏。插入兩條記錄之後,該N號塊的ITL槽的信息改成 [Undo_block2 / SCN9.11]。事務
那麼當執行查詢的服務器進程掃描到N號塊時,發現SCN9.11大於SCN9.00,因而到ITL槽中指定的 Undo_block2處找到該undo塊。發現該undo塊裏記錄的ITL信息爲[Undo_block1 / SCN9.10],其中的SCN9.10仍然大於SCN9.00,因而服務器進程繼續根據ITL中記錄的Undo_block1,找到該undo塊。發現該undo塊裏記錄的ITL信息爲[Undo_block0 / SCN8.50],這時ITL裏的SCN8.50小於發出查詢時的SCN9.00,說明這時undo塊包含合適的undo信息,因而服務器進程再也不找下去,而是將N號塊、Undo_block2以及Undo_block1的數據結合起來,構建CR塊。將當前N號的數據複製到CR塊裏,而後在CR塊裏先回退9點11分的事務,也就是在CR塊裏刪除兩條記錄,而後再回退9點10分的事務,也就是在CR塊裏插入被刪除的記錄,從而構建出9點鐘時的數據。 Oracle就是這樣,以層層嵌套的方式,查找整個undo塊的鏈表,直到發現ITL槽裏的SCN號小於等於發出查詢時的那個SCN號爲止。正常來講,當前undo塊裏記錄的SCN號要比上一個undo塊裏記錄的SCN號要小。get
可是在查找的過程當中,可能會發現當前undo塊裏記錄的ITL槽的SCN號比上一個undo塊裏記錄的SCN號還要大。這種狀況說明因爲事務被提交或回滾,致使當前找到的undo塊裏的數據已經被其餘事務覆蓋了,因而咱們沒法再找出小於等於發出查詢時的那個時間點的SCN號,這時Oracle就會拋出一個很是經典的錯誤——ORA-1555,也就是snapshot too old的錯誤。
回滾事務則是在執行DML之後,發出rollback命令撤銷DML所做的變化。Oracle利用記錄在ITL槽裏記錄的undo 塊的地址找到該undo塊,而後從中取出變化前的值,並放入數據塊中,從而對事務所做的變化進行回滾。
實例恢復則是在SMON進程完成前滾並打開數據庫之後發生。SMON進程會去查看undo segment頭部(所謂頭部就是undo segment裏的第一個數據塊)記錄的事務表(每一個事務在使用undo塊時,首先要在該undo塊所在的undo segment的頭部記錄一個條目,該條目裏記錄了該事務相關的信息,其中包括是否提交等),將其中既沒有提交也沒有回滾,而是在實例崩潰時被異常終止的事務所有回滾
oracle視頻教程請關注:http://down.51cto.com/4202939/up