事務(wù)隔離
ITL
ITL: Interested Transaction List,也叫事務(wù)槽,它位于BLOCK Header。格式如下:
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0001.026.00000150 0x008003c0.0113.17 ---- 1 fsc 0x0000.00000000
0x02 0x0005.013.00000190 0x0080004b.013a.17 ---- 1 fsc 0x0001.00000000
Xid:事務(wù)id,在回滾段事務(wù)表中有一條記錄和這個(gè)事務(wù)對(duì)應(yīng),分別是undo segment number+undo segment slot+undo segment sequence,也就是回滾段編號(hào)、回滾段事務(wù)槽號(hào)、和回滾段的序號(hào)(回滾段事務(wù)槽被覆蓋的次數(shù))
Uba:回滾段地址,該事務(wù)對(duì)應(yīng)的回滾段地址
Flag:事務(wù)標(biāo)志位。這個(gè)標(biāo)志位就記錄了這個(gè)事務(wù)的操作,各個(gè)標(biāo)志的含義分別是:
——- = 事務(wù)是活動(dòng)的,或者在塊清除前提交事務(wù)
C—- = 事務(wù)已經(jīng)提交并且清除了行鎖定。
-B— = this undo record contains the undo for this ITL entry
—U- = 事務(wù)已經(jīng)提交(SCN已經(jīng)是最大值),但是鎖定還沒有清除(快速清除)。
—-T = 當(dāng)塊清除的SCN被記錄時(shí),該事務(wù)仍然是活動(dòng)的,塊上如果有已經(jīng)提交的事務(wù),
Lck:影響的記錄數(shù)
Scn/Fsc:快速提交(Fast Commit Fsc)的SCN或者Commit SCN。其實(shí)就是事務(wù)號(hào)。
SCN
system change number (SCN)是一個(gè)非常重要的標(biāo)記,Oracle使用它來標(biāo)記數(shù)據(jù)庫(kù)在過去時(shí)間內(nèi)的狀態(tài)和軌跡。Oracle使用SCN來保存所有變化的軌跡。SCN是一個(gè)邏輯時(shí)鐘來記錄數(shù)據(jù)庫(kù)事件。
隔離
當(dāng)發(fā)出一條sql語(yǔ)句時(shí),ORACLE會(huì)記錄下這個(gè)時(shí)刻(SCN),然后在buffer cache中查找需要的BLOCK,或者從磁盤上讀。當(dāng)別的會(huì)話修改了數(shù)據(jù),或者正在修改數(shù)據(jù),就會(huì)在相應(yīng)的block上記錄ITL,此時(shí)ORACLE發(fā)現(xiàn)ITL中記錄的SCN(Scn/Fsc)大于SELECT時(shí)刻的SCN,或者ITL沒有SCN(正在修改,未提交);那么ORACLE就會(huì)根據(jù)ITL中的Uba找到UNDO信息獲得該block的前鏡像,然后在buffer cache 中構(gòu)造出CR(consistent read)塊,此時(shí)ORALCE 也會(huì)檢查構(gòu)造出來的BLOCK中ITL記錄的SCN(Scn/Fsc),如果SCN(Scn/Fsc)還大于select時(shí)刻的SCN,那么一直重復(fù)構(gòu)造前鏡像,然后ORACLE找到前鏡像BLOCK中的ITL的SCN是否小于select的SCN,同時(shí)檢查這個(gè)事物有沒有提交或者回滾,如果沒有,那么繼續(xù)構(gòu)造前鏡像,直到找到需要的BLOCK,如果在構(gòu)造前鏡像的過程中所需的UNDO信息被覆蓋了,就會(huì)報(bào)快照過舊的錯(cuò)誤。

行級(jí)鎖
鎖分共享鎖和排他鎖,查詢使用共享鎖,修改使用排他鎖,所以事務(wù)對(duì)修改的數(shù)據(jù)需要加排他鎖,例如行級(jí)鎖。行級(jí)鎖實(shí)現(xiàn)通過對(duì)數(shù)據(jù)記錄設(shè)置itl的序號(hào),來說明數(shù)據(jù)記錄已經(jīng)被這個(gè)itl設(shè)置。
----------------
Start dump data blocks tsn: 0 file#: 1 minblk 61186 maxblk 61186
buffer tsn: 0 rdba: 0x0040ef02 (1/61186)
scn: 0x0000.000d206f seq: 0x01 flg: 0x02 tail: 0x206f0601
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
----------------
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0001.026.00000150 0x008003c0.0113.17 ---- 1 fsc 0x0000.00000000
0x02 0x0005.013.00000190 0x0080004b.013a.17 --U- 1 fsc 0x0001.000d206f
-----------------
block_row_dump:
tab 0, row 0, @0x1f6e
tl: 13 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 02
col 1: [ 6] 6f 72 61 63 6c 65
tab 0, row 1, @0x1f55
tl: 12 fb: --H-FL-- lb: 0x2 cc: 2 --對(duì)應(yīng)itl 0x02
col 0: [ 2] c1 03
col 1: [ 5] 6d 79 73 71 6c
tab 0, row 2, @0x1f61
tl: 13 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 04
col 1: [ 6] 6f 72 61 63 6c 65
end_of_block_dump
ITL Cleanout和Delayed block cleanout 和快速提交
在事務(wù)提交(commit)前,會(huì)在數(shù)據(jù)塊的頭部記錄下這個(gè)Cleanout SCN(Csc)號(hào)、Undo Block Address(Uba)和Transaction ID(Xid);并且在在對(duì)應(yīng)Interested Transaction List(Itl)中設(shè)置鎖標(biāo)志,記錄這個(gè)事務(wù)在這數(shù)據(jù)塊中產(chǎn)生的鎖的數(shù)目;同時(shí)在對(duì)應(yīng)修改的數(shù)據(jù)記錄上打上行級(jí)鎖標(biāo)志,并映射到對(duì)應(yīng)的Itl去。當(dāng)提交時(shí),并不會(huì)一一清除掉所有鎖標(biāo)志,而是給對(duì)應(yīng)的Itl打上相應(yīng)標(biāo)志,告訴后面訪問該數(shù)據(jù)塊的事務(wù),相應(yīng)的事務(wù)已經(jīng)提交。這就叫做快速提交(Fast Commit)。
ITL Cleanout
一個(gè)新的事務(wù)過來時(shí),它首先會(huì)選擇一個(gè)itl槽,首先oracle采用C---狀態(tài)的事務(wù),如果沒有C---狀態(tài)的事務(wù),oracle就會(huì)發(fā)生一次itl cleanout,檢查所有的ITL相關(guān)的事務(wù),如果確認(rèn)事務(wù)已經(jīng)提交了,就將之修改為C---狀態(tài)了。
Delayed block cleanout
1.如果一個(gè)transaction修改的block超過db cache的10%.
2.當(dāng)一個(gè)事務(wù)未提交時(shí),其修改過的block就已經(jīng)寫到硬盤上去了。此時(shí)事務(wù)提交了,并不會(huì)修改數(shù)據(jù)塊上的狀態(tài)。
僅僅為修改狀態(tài)來重寫數(shù)據(jù)塊代價(jià)比較高,所以采用延遲修改。
可以看出塊有可能在修改過程中,事務(wù)還沒有commit的時(shí)候被寫進(jìn)磁盤。
已經(jīng)提交的事務(wù)可以不用修改itl,從事務(wù)表中可以找到相應(yīng)事務(wù)的狀態(tài),所以最終不會(huì)對(duì)結(jié)果造成任何影響,ITL Cleanout的時(shí)候也是這樣查詢真實(shí)的事務(wù)狀態(tài)。
事務(wù)表?xiàng)l目有限,ITL Delayed block cleanout 丟失了事務(wù)表中的信息怎么辦?
TRN TBL::
index state cflags wrap# uel scn dba parent-xid nub stmt_num cmt
------------------------------------------------------------------------------------------------
0x00 9 0x00 0x5b1a5 0x001c 0x0000.07ecd2be 0x0080030b 0x0000.000.00000000 0x00000002 0x00000000 1398758386
0x01 9 0x00 0x5b170 0x0011 0x0000.07ecd32d 0x00800326 0x0000.000.00000000 0x00000001 0x00000000 1398758405
0x02 9 0x00 0x5b1a0 0x000c 0x0000.07ecd2d7 0x0080030b 0x0000.000.00000000 0x00000001 0x00000000 1398758403
0x03 9 0x00 0x5b1a8 0x0000 0x0000.07ecd2b7 0x0080030a 0x0000.000.00000000 0x00000001 0x00000000 1398758386
0x04 9 0x00 0x5b1ad 0x000d 0x0000.07ecd2e4 0x0080030c 0x0000.000.00000000 0x00000002 0x00000000 1398758404
0x05 9 0x00 0x5b1a3 0x0021 0x0000.07ecd25d 0x0080030a 0x0000.000.00000000 0x00000001 0x00000000 1398758346
0x06 10 0x80 0x5b1ab 0x0002 0x0000.07ecd29c 0x00800326 0x0000.000.00000000 0x00000001 0x00000000 0
0x07 9 0x00 0x5b1a3 0x0012 0x0000.07ecd2ee 0x00000000 0x0000.000.00000000 0x00000000 0x00000000 1398758404
0x06的State 10表示這個(gè)回滾段對(duì)應(yīng)的事務(wù)是active狀態(tài)。如果發(fā)現(xiàn)事務(wù)表中的條目被覆蓋了,例如itl記載的序列為100,現(xiàn)在為120,則認(rèn)為事務(wù)已經(jīng)提交,沒有提交的事務(wù)為active,oracle是不會(huì)覆蓋的。
undo, redo
Undo日志記錄某數(shù)據(jù)被修改前的值,可以用來在事務(wù)失敗時(shí)進(jìn)行rollback;Redo日志記錄某數(shù)據(jù)塊被修改后的值,可以用來恢復(fù)未寫入data file的已成功事務(wù)更新的數(shù)據(jù)。但是數(shù)據(jù)庫(kù)崩潰了之后從日志的什么位置進(jìn)行恢復(fù)?就引出了檢查點(diǎn)的概念。
redo日志應(yīng)首先持久化在磁盤上,然后事務(wù)的操作結(jié)果才寫入db buffer,(此時(shí),內(nèi)存中的數(shù)據(jù)和data file對(duì)應(yīng)的數(shù)據(jù)不同,我們認(rèn)為內(nèi)存中的數(shù)據(jù)是臟數(shù)據(jù)),db buffer再選擇合適的時(shí)機(jī)將數(shù)據(jù)持久化到data file中。這種順序可以保證在需要故障恢復(fù)時(shí)恢復(fù)最后的修改操作。先持久化日志的策略叫做Write Ahead Log,即預(yù)寫日志。
WAL寫日志 -> 修改db buffer -> 寫入data file。
redo是對(duì)應(yīng)的數(shù)據(jù)文件,undo是數(shù)據(jù)文件的一部分,所以u(píng)ndo的修改是需要進(jìn)redo日志的,這個(gè)策略解決很大問題
檢查點(diǎn)
WAL導(dǎo)致寫數(shù)據(jù)文件是"異步"的,寫日志文件是"同步"的。
這就可能導(dǎo)致數(shù)據(jù)庫(kù)實(shí)例崩潰時(shí),內(nèi)存中的DB_Buffer 中的修改過的數(shù)據(jù),可能沒有寫入到數(shù)據(jù)塊中。數(shù)據(jù)庫(kù)在重新打開時(shí),需要進(jìn)行恢復(fù),來恢復(fù)DB Buffer 中的數(shù)據(jù)狀態(tài),并確保已經(jīng)提交的數(shù)據(jù)被寫入到數(shù)據(jù)塊中。檢查點(diǎn)是這個(gè)過程中的重要機(jī)制,通過它來確定,恢復(fù)時(shí)哪些重做日志應(yīng)該被掃描并應(yīng)用于恢復(fù)。
要了解這個(gè)檢查點(diǎn),首先要知道checkpoint queue概念,檢查點(diǎn)發(fā)生后,觸發(fā)DBWn,CKPT獲取發(fā)生檢查點(diǎn)時(shí)對(duì)應(yīng)的SCN,通知DBWn要寫到這個(gè)SCN為止, DBWR寫dirty buffer 是根據(jù) buffer 在被首次 modify的時(shí)候的時(shí)間的順序?qū)懗觯簿褪?buffer被modify 的時(shí)候會(huì)進(jìn)入一個(gè)queue (checkpoint queue),DBWr 就根據(jù)queue從其中批量地寫到數(shù)據(jù)文件。 由于這里有一個(gè)順序的關(guān)系,所以 dbwr的寫的進(jìn)度就是可衡量的,寫到哪個(gè)buffer的時(shí)候該buffer的首次變化時(shí)候的scn就是當(dāng)前所有數(shù)據(jù)文件block的最新scn,但是由于無(wú)法適時(shí)的將dbwr的進(jìn)度記錄下來,所以oracle 選擇了一些策略。 其中就包括ckpt進(jìn)程的檢查點(diǎn)和心跳。
恢復(fù)
當(dāng)數(shù)據(jù)庫(kù)突然崩潰,而還沒有來得及將buffer cache里的臟數(shù)據(jù)塊刷新到數(shù)據(jù)文件里,同時(shí)在實(shí)例崩潰時(shí)正在運(yùn)行著的事務(wù)被突然中斷,則事務(wù)為中間狀態(tài),也就是既沒有提交也沒有回滾。這時(shí)數(shù)據(jù)文件里的內(nèi)容不能體現(xiàn)實(shí)例崩潰時(shí)的狀態(tài)。這樣關(guān)閉的數(shù)據(jù)庫(kù)是不一致的。
下次啟動(dòng)實(shí)例時(shí),Oracle會(huì)由SMON進(jìn)程自動(dòng)進(jìn)行實(shí)例恢復(fù)。實(shí)例啟動(dòng)時(shí),SMON進(jìn)程會(huì)去檢查控制文件中所記錄的、每個(gè)在線的、可讀寫的數(shù)據(jù)文件的END SCN號(hào)。
roll forward (前滾)
SMON進(jìn)程進(jìn)行實(shí)例恢復(fù)時(shí),會(huì)從控制文件中獲得檢查點(diǎn)位置。于是,SMON進(jìn)程到聯(lián)機(jī)日志文件中,找到該檢查點(diǎn)位置,然后從該檢查點(diǎn)位置開始往下,應(yīng)用所有的重做條目,從而在buffer cache里又恢復(fù)了實(shí)例崩潰那個(gè)時(shí)間點(diǎn)的狀態(tài)。這個(gè)過程叫做前滾,前滾完畢以后,buffer cache里既有崩潰時(shí)已經(jīng)提交還沒有寫入數(shù)據(jù)文件的臟數(shù)據(jù)塊,也還有事務(wù)被突然終止,而導(dǎo)致的既沒有提交又沒有回滾的事務(wù)所弄臟的數(shù)據(jù)塊。
rollback(回滾)
前滾一旦完畢,SMON進(jìn)程立即打開數(shù)據(jù)庫(kù)。但是,這時(shí)的數(shù)據(jù)庫(kù)中還含有那些中間狀態(tài)的、既沒有提交又沒有回滾的臟塊,這種臟塊是不能存在于數(shù)據(jù)庫(kù)中的,因?yàn)樗鼈儾]有被提交,必須被回滾。打開數(shù)據(jù)庫(kù)以后,SMON進(jìn)程會(huì)在后臺(tái)進(jìn)行回滾。
必須先前滾,在回滾
回滾段實(shí)際上也是以回滾表空間的形式存在的,既然是表空間,那么肯定就有對(duì)應(yīng)的數(shù)據(jù)文件,同時(shí)在buffer cache 中就會(huì)存在映像塊,這一點(diǎn)和其他表空間的數(shù)據(jù)文件相同。
當(dāng)發(fā)生DML操作時(shí),既要生成REDO(針對(duì)DML操作本身的REDO Entry)也要生成UNDO(用于回滾該DML操作,記錄在UNDO表空間中),但是既然UNDO信息也是使用回滾表空間來存放的,那么該DML操作對(duì)應(yīng)的UNDO信息(在BUFFER CACHE生成對(duì)應(yīng)中的UNDO BLOCK)就會(huì)首先生成其對(duì)應(yīng)的REDO信息(UNDO BLOCK's REDO Entry)并寫入Log Buffer中。
這樣做的原因是因?yàn)锽uffer Cache中的有關(guān)UNDO表空間的塊也可能因?yàn)閿?shù)據(jù)庫(kù)故障而丟失,為了保障在下一次啟動(dòng)時(shí)能夠順利進(jìn)行回滾,首先就必須使用REDO日志來恢復(fù)UNDO段(實(shí)際上是先回復(fù)Buffer Cache中的臟數(shù)據(jù)塊,然后由Checkpoint寫入U(xiǎn)NDO段中),在數(shù)據(jù)庫(kù)OPEN以后再使用UNDO信息來進(jìn)行回滾,達(dá)到一致性的目的。
生成完UNDO BLOCK's REDO Entry后才輪到該DML語(yǔ)句對(duì)應(yīng)的REDO Entry,最后再修改Buffer Cache中的Block,該Block同時(shí)變?yōu)榕K數(shù)據(jù)塊。
實(shí)際上,簡(jiǎn)單點(diǎn)說REDO的作用就是記錄所有的數(shù)據(jù)庫(kù)更改,包括UNDO表空間在內(nèi)。
事務(wù)
- 首先當(dāng)一個(gè)開始時(shí),需要在回滾段事務(wù)表上分配一個(gè)事務(wù)表(事務(wù)槽)。
- 在數(shù)據(jù)塊頭部獲取一個(gè)ITL事務(wù)槽,該事務(wù)槽指向回滾段頭的事務(wù)槽。
- 在修改數(shù)據(jù)之前,需要記錄前鏡像信息,這個(gè)信息以UNDO RECORD的形式存儲(chǔ)在回滾段中,回滾段頭事務(wù)槽指向該記錄。
- 鎖定修改行,修改行鎖定位(1b-lock-byte)指向ITL事務(wù)槽。
- 記錄redo日志。
- 修改數(shù)據(jù)。
- 提交日志,日志號(hào)為提交時(shí)的scn。
問題
UNDO日志沒有持久化到磁盤,rollback怎么辦?
生成UNDO日志時(shí),UNDO信息是使用回滾表空間來存放的,相當(dāng)于修改數(shù)據(jù),會(huì)生成相應(yīng)的REDO日志。所以恢復(fù)時(shí)需要先前滾后回滾,前滾可以恢復(fù)回滾表空間。
UNDO REDO datafile
redo--> undo-->datafile
insert一條記錄時(shí),表跟undo的信息都會(huì)放進(jìn) redo 中,在commit 或之前, redo 的信息會(huì)放進(jìn)硬盤上。 故障時(shí), redo 便可恢復(fù)那些已經(jīng)commit 了的數(shù)據(jù)。
AWL, 任何修改操作需要先寫日志成功, 但是orcale在提交時(shí)才將日志寫磁盤,詳情將下節(jié)。commit,提交僅僅是生成事務(wù)號(hào),提交當(dāng)前的scn,記錄下提交標(biāo)志,將REDO日志刷到磁盤。
一次事務(wù)嚴(yán)格要執(zhí)行多少次刷盤 【猜測(cè)】
首先生成UNDO日志,相應(yīng)的要生成REDO日志,如果有多條UNDO日志,最后只調(diào)用一次刷盤就行,因?yàn)榇藭r(shí)還沒修改數(shù)據(jù)。
一種策略:
1.批量寫入U(xiǎn)NDO的REDO日志。
2.刷盤
3.寫UNDO日志
4.批量寫入數(shù)據(jù)修改的REDO日志。
5.批量修改DBbuffer。
6.提交。
7.刷盤。
提交事務(wù)的時(shí)候,生成事務(wù)號(hào),寫提交標(biāo)志,這個(gè)時(shí)候REDO需要刷盤,成功刷盤意味著事務(wù)成功提交。
一次事務(wù)基本需要2次刷盤。
另外一種:【mysql一次事務(wù)一次刷盤配置是這種】
用戶只在提交事務(wù)時(shí)強(qiáng)制sync一次。為了避免數(shù)據(jù)修改一部分down機(jī),undo數(shù)據(jù)的redo還沒寫盤問題。采取的解決還是日志先行策略,Btree中的數(shù)據(jù)寫入磁盤前,數(shù)據(jù)塊對(duì)應(yīng)的redo要先寫到磁盤中。
總之,發(fā)起事務(wù)方強(qiáng)制在提交事務(wù)時(shí)sync一次,數(shù)據(jù)庫(kù)機(jī)制保證在數(shù)據(jù)塊寫磁盤時(shí),強(qiáng)制相應(yīng)redo先入盤。一般的情況是數(shù)據(jù)修改在內(nèi)存中,undo的redo日志以及數(shù)據(jù)塊的redo日志都在內(nèi)存中,當(dāng)數(shù)據(jù)庫(kù)要將數(shù)據(jù)塊寫盤時(shí),會(huì)先將對(duì)應(yīng)的redo日志寫盤,undo的redo日志在數(shù)據(jù)塊的redo日志之前,所以不會(huì)有什么問題。
參考:
【1】http://www.dbaxiaoyu.com/archives/1987
【2】https://blog.csdn.net/tianlesoftware/article/details/7346212
【3】http://www.cnblogs.com/polestar/archive/2013/03/11/2953716.html
【4】https://blog.csdn.net/kobejayandy/article/details/50885693
【5】https://blog.csdn.net/tianlesoftware/article/details/5251916
【6】https://blog.csdn.net/wanghui5767260/article/details/20715809
【7】https://blog.csdn.net/dba_waterbin/article/details/7823519
【8】https://blog.csdn.net/wanghai__/article/details/4730722