oracle-1條updata的故事

客戶端SQL Plus請求鏈接,監聽接受客戶端的TCP鏈接,並獲取客戶端發過來的TNS數據包。
監聽進程打開用於與子進程通訊的管道,同時fork一個子進程,稱爲「監聽子進程1」的子進程,而後監聽進程一直等待,直到這個「監聽子進程1」結束。
監聽子進程1 Fork出子進程2。
完成上面一步,子進程1立刻退出並結束子進程1。
子進程2收集本進程所在的主機名、IP地址及進程號等信息,並把子進程2重名成server process(這裏咱們也把server process叫前臺進程或叫服務器進程),申請佔用一小塊PGA內存。
前臺進程把主機名、IP地址及進程號發送給監聽進程。
監聽進程收到前臺進程的信息,並返回客戶端的信息(好比用戶密碼環境變量等)給前臺進程。
前臺進程查詢USER$、PROFILE$等數據字典,校驗用戶名密碼是否合法,若是用戶密碼錯誤就報錯用戶名密碼無效,不然就與客戶端進行交互。
客戶端收到前臺進程的信息與之交互,整個鏈接建立完成。
客戶端發起一個鏈接,服務器端的監聽建立一個shadow process,並把這個影子進程指派給用戶,後期用戶全部的操做都提交給shadow,shadow會替用戶完成數據庫內的操做,並把結果返回給用戶。(中間件有點不一樣)
客戶端發起update語句,根據客戶端環境字符集轉換成對應的編碼,傳到服務器端,
數據庫端會的server進程,與用戶進程組成一個會話。而後在server端的pga區,處理sql請求。服務器端根據服務器端字符集轉換。
process接受語句,放在PGA中,將修改前的值放在PGA
在用戶的private sql area區域,每一個字符包括空格轉化成ASCII碼後,再拿這一堆ASCII碼經過HASH函數生成一個sql_hash值
肯定這條語句的執行計劃
搜索當前用戶的session緩存中(在PGA中的UGA)是否存在相同的散列版本。
若是存在,則直接經過遊標link到位於PGA的private SQL AREA( private SQL area),此時成爲軟軟解析。
若是不存在,檢查初始化參數SESSION_CACHED_CURSORS是否被設置,若是被設置,且有相同的sql,則一樣能夠經過遊標指向到位於PGA的私有SQL AREA,不然,結束軟軟解析,嘗試軟解析。
若是不存在,建立一個遊標。拿這個值去shared pool中找執行計劃,根據hash值,判斷這個塊應該在哪一個桶中。
從內存中申請一個lock structure,在其中記錄「鎖模式,進程ID」等重要信息
而後看可否馬上得到資源訪問權,若是不能,則把這個lock structure 掛到resource structure的waiter鏈表中,若是能得到,則把lock structure 掛到resource structure的owner鏈表中。
鎖定數據塊頭鏈表,逐個遍歷上面的塊頭,看有沒有與這條語句相同的哈希值
若是有,若是檢查到共享庫中有一個語句具備相同的哈希值,則數據庫執行語義和環境檢查, 以肯定其含義是否相同。即便兩個語句在語義上是相同的,某個環境差別也可能使其強制進行硬解 析。在這種狀況下,環境是能夠影響執行計劃生成的所有會話設置, 如工做區大小或優化器設置等。取出,執行軟解析
不然,執行硬解析
檢查sql的語法、語義、權限,
查詢相關的數據字典
根據CBO或者RBO生成執行計劃
CBO,直方圖,動態採樣
Oracle首先掃描shared pool的freelist中是否有足夠空間,若是有則使用,若是沒有足夠的空間,則判斷這次內存請求是一次large請求,仍是一次small請求 。
如果large請求,則在reserved pool 查找是否有可用的空間,若是找到了可用的內存(chunk)則做size檢查,並對內存(chunk)作截斷操做,截取所需內存大小使用,若是在reserved pool 中依然沒有找到可用的內存(chunk),會重複上一步,若是依然沒有找到,則對reserved pool 中的對象作LRU算法的age out操做,age out一些reserved pool 中的對象,來知足本次的內存(chunk)請求操做,若是仍是沒有找到可用的內存,重複LRU算法,直到找到可用內存(chunk)。
如果small請求,則在shared pool 的free list中查找是否有可用的內存(chunk),若是找到可用的內存(chunk)則做size檢查,並對內存(chunk)作截斷操做,截取所需內存大小使用,若是沒有找到,則對shared pool中的對象作LRU算法的age out操做,並再次查找是否有可用的內存,知道找到可用的chunk
shared pool
    查找空閒內存,若是沒有發現大小正好合適的空閒chunk,就查找更大的chunk,若是找到比請求的大小更大的空閒chunk,則將它分裂,寫入執行計劃,掛到library cache的鏈上,多餘部分繼續放到空閒列表中。由於過多的硬解析加重了內存段分配的需求,這樣就產生了碎片問題。系統通過長時間運行後,就會產生大量小的內存碎片。當請求分配一個較大的內存塊時,儘管shared pool總空閒空間還很大,可是沒有一個單獨的連續空閒塊能知足須要。這時,就可能產生 ORA-4031錯誤。
完成硬解析
完成解析
若是使用了,綁定變量,將實際的變量值代入SQL語句中。
讀取要修改的數據塊。
在dbbuffer中找要修改的塊
查詢SEG$等數據字典,找到要修改表段頭
從段頭讀出Extent Map,按執行計劃開始掃描數據塊
將塊所在的file#,block#哈希算計算後,找相應hash bucket,得到保護這個bucket的cbc latch
遍歷桶中全部CBC鏈。得到cache buffers chains latch,遍歷那條buffer chain直到找到須要的buffer header。此時須要注意,若是執行計劃是走全表掃描,或者是帶有惟一約束的索引(update),是以獨佔模式獲取CBC鏈,會引發CBC鏈busy。
比較buffer header上所記錄的數據塊的地址(rdba),若是不符合,則跳過該buffer header。
 跳過狀態爲CR的buffer header。(說明有別的進程正在進行一致性讀,因此才構造了這個cr塊,若是我也要找這個塊的原塊,我須要本身再從新構造一個新的cr塊,不會使用這個舊的cr塊,若是我不是找這個塊的原塊,那我不須要構造,因此這兩種狀況下都是跳過cr塊)
 若是遇到狀態爲READING(正在從磁盤上讀出的數據塊)的buffer header,則等待,一直等到該buffer header的狀態改變之後再比較所記錄的數據塊的地址是否符合。(說不定是以前的查詢,有可能就是這條sql語句,也有多是以前的(本身用戶或者其餘用戶的sql)語句,正好也須要讀這個塊內的數據,正在往內存裏讀,這下我就能夠直接用前輩的努力就能夠了)
block在內存中。若是發現數據塊地址符合的buffer header,則查看該buffer header是否位於正在使用的列表上,若是是位於正在使用的列表上,則判斷已存在的鎖定模式與當前所要求的鎖定模式是否兼容,若是是兼容的,則返回該buffer header所記錄的數據塊地址,並將當前進程號放入該buffer header所處的正在使用的列表上
根據須要進行的操做類型(讀或寫),它須要在buffer header上得到一個共享或獨佔模式的buffer pin或者buffer lock
若進程得到buffer header pin,它會釋放得到的cache buffers chains latch,而後執行對buffer block的操做,若進程沒法得到buffer header pin,它就會在buffer busy waits事件上等待。(進程之因此沒法得到buffer header pin,是由於爲了保證數據的一致性,同一時刻一個block只能被一個進程pin住進行存取,所以當一個進程須要存取buffer cache中一個被其餘進程使用的block的時候,這個進程就會產生對該block的buffer busy waits事件。)
在段頭塊上仍是目標塊?加上TM、TX鎖,獲取目標塊的latch,經過塊頭找到塊在內存中的地址,
若是有鎖的衝突,則產生阻塞。
wait方式
pin :當一個會話沒法得到須要的latch時,會繼續使用CPU(CPU空轉),達到一個間隔後,再次嘗試申請latch,直到達到最大的重試次數。
sleep:當 一個會話沒法得到須要的latch時,會等待一段時間(sleep),達到一個間隔後,再次嘗試申請latch,如此反覆,直到達到最大的重試次數。
no wait方式
不會發生sleep或者spin., 轉而去獲取其它可用的Latch
若是是乾淨塊,修改完後,掛到ckpt鏈上,在LRU鏈上的使用次數+1,(特定條件後)移動到LRUW上,並添加到檢查點鏈
block在硬盤。 若是比較完整個hash chain之後還沒發現所要找的buffer header,則從磁盤上讀取數據文件。
會話產生一個shadow process將塊讀到內存,
在讀取數據以前,Server進程須要掃描輔助LRU List尋找Free的Buffer,掃描過程當中Server進程會把發現的全部已經被修改過的Buffer註冊到LRUW List上
若是Server進程掃描LRU超過一個閥值仍然不能找到足夠的Free Buffer,將中止尋找,轉而通知DBWn去寫出髒數據,釋放內存空間。這時進程會處於free buffer wait等待
(非全表掃描)找到足夠的Buffer以後,Server進程就能夠將Buffer從數據文件讀入Buffer Cache,並將buffer移動到主LRU,若是非全表掃描較多,輔助LRU中塊會愈來愈少,爲了保持比例(輔助LRU佔整個LRU總塊數的20%到25%左右),SMON進程會3秒一次持有LRU
Latch,將主LRU冷端末的塊移到輔助LRU。全表掃描也先到輔助LRU中尋找可用塊,但全表掃描的塊仍將留在輔助LRU,不會調往主LRU冷端頭。所以全表掃描的塊將很快被覆蓋。全表
掃描操做將只使用輔助LRU(也會用到主LRU,只會用到不多量的主LRU),一次大的全掃操做,能夠將輔助LRU的全部塊覆蓋一遍或多遍。數據庫剛啓動時,或剛Flush Buffer_cache時,全部塊會被放在輔助LRU中。前臺進程(服務器進程)掃描主、輔LRU時,會將遇到的TCH爲2如下的髒塊,移到主LRUW。
將塊所在的file#,block#哈希算計算後,找相應hash bucket,得到保護這個bucket的cbc latch
將讀取到的數據塊所對應的buffer header掛到CBC(cache buffer chain)鏈上,塊放到對應鏈的buffer上,頭的地址指向鏈上的對應的buffer塊。
物理/邏輯一致性檢查。(計算這個數據塊的校驗和,並和塊頭的字段相比較,若是有差別,oracle就知道這個塊有錯誤,會報出ORA-1578錯誤,會對數據塊作cache recovery,若是不能把數據塊恢復到一致狀態,oracle會把這個數據塊標誌位software corrupt。若是是其餘錯誤,則須要使用dbms_repair包吧數據塊標識爲「software corrupt」,塊頭的校驗和字段在寫回磁盤前會進行從新計算。執行與否受參數db_block_checksum影響)
Dbv只檢查數據塊的header/footer,作邏輯驗證;
Db_block_checking:替代10210/10211/10212事件,進行塊完整性檢查,如free slot list/行位置/鎖數量;檢查時會複製塊,若有錯誤將塊標誌爲soft corruption;
Db_block_checksum:dbwr和direct loader寫數據塊時計算checksum並存於cache層chkval,再次讀取時從新計算並與已有checksum比較;
Dbms_repair修復cache/transaction層的錯誤,將塊標示爲soft corruption;
對數據塊進行邏輯一致性檢查。檢查失敗會拋出ORA-1578的internal錯誤。當oracle檢查到數據塊的邏輯一致性時,會對數據塊作cache recovery,若是不能把數據塊恢復到一致狀態,oracle會把這個數據塊標誌位software corrupt,當有查詢訪問到這個數據塊時,也會拋出ora-1578錯誤。若是不是拋出ORA-1578,則須要先使用dbms_repair包。
先進行一致讀,找出要修改的行
在LRU鏈的冷端找一個乾淨的塊,將數據塊放到其中,若是尋找達到必定數目的塊,還未找到乾淨塊,則將LRU鏈冷端的髒塊,遷移到LRUW鏈上,?
釋放cbc latch
檢查要修改的行上有沒有鎖標記
若是有,根據對應的事務槽,找到相應的回滾段,看事務表中記錄的事務是否提交
若是提交,去處所標記,將對應的修改事務槽事務狀態爲U,進行下一步。(C=Commited;U=Commited Upper Bound;T=Active at CSC;---是未提交 )
若是回滾段事務表中事務信息被覆蓋,則認爲事務已經提交,去處鎖標記,將對應的修改事務槽事務狀態爲U,進行下一步。
若是未提交,則等待其提交,產生buffer busy 等待事件
若是沒有,進行下一步。
將該BH的標記設置爲釘住(ping)
檢查有沒有before類型觸發器,若是有執行(在此過程當中:new的值可能會被從新賦值)。
隨後,oracle以當前模式讀這個塊,若是查找這一行的(where)條件列已經修改過。因爲使用條件列來定位這條記錄,並且條件列已經修改,因此數據庫會重啓查詢。
從shared pool中分配的一塊內存劃分給一個private stand,並受到redo allocation latch的保護,這個事務生成的redo存放在private stand中,當flush private stand 或者commit時,private stand被批量寫入log文件中,對於使用Private strand的事務,無需先申請Redo Copy Latch,也無需申請Shared Strand的redo allocation latch,而是flush或commit是批量寫入磁盤,所以減小了Redo Copy Latch和redo allocation latch申請/釋放次數、也減小了這些latch的等待,從而下降了CPU的負荷。(redo log 最開始是在pga中的uga產生的(數據庫通常是專有模式),oracle會把它拷貝到SGA中的log_buffer中去,若是log_buffer太小,或者lgwr不可以快速將redo 寫入到log file中,那麼就會產生log buffer space等待事件,遇到此類問題,能夠增長 log_buffer大小,調整log file 到裸設備,I/0快的磁盤中)
在PGA中生成DataBlock塊的後映像(11.9)。
在PGA中生成UNDO段頭事務表的後映像(5.2)。
在PGA中生成UNDO塊的後映像(5.1)
將前三個Redo矢量作爲一條Redo Recorder寫入Shared pool中的Private strand。
將DataBlock中的前映像值,寫入Shared pool中的Imu pool。
修改UNDO段頭的事務表。
修改UNDO塊,寫入DataBlock的前映像。
將每條還原改變向量寫到對應的IMU池,或者依照舊的機制;
將每條重作改變向量寫到私有redo去;
若是新事務申請不到private stand的redo allocation latch,則會繼續遵循舊的redo buffer機制,申請寫入shared strand中。
在PGA中構建change vector並組合成redo record
在PGA中生成UNDO段頭事務表的後映像(5.2)
在PGA中生成UNDO塊的後映像(5.1)
在PGA中生成DataBlock塊的後映像(11.9)
將前三個Redo矢量作爲一條Redo Recorder寫入Log buffer
修改UNDO段頭的事務表,事務正式開始。
修改UNDO塊,寫入DataBlock的前映像。
改變這個塊
取消block 的pin標記
修改DataBlock,將新值寫入Buffer cache。
調用kcrfwr()將record寫入log buffer:
計算record佔用的空間大小;分配SCN;
獲取copy latch,驗證SCN;
獲取allocation latch,檢驗log buffer/file是否有足夠空間,有則釋放allocation latch將redo寫入log buffer,不然同時釋放allocation/copy latch並通知LGWR進行log flush/switch;(爲防止多個進程同時通知LGWR刷新redo或切換日誌文件,引入write latch(只有1個),只有獲取此latch後才能進行下一步操做;)
將redo record寫至log buffer然後釋放copy latch,檢查是否達到觸發LGWR閾值;
更新BH對應的內存數據塊的內容。
更新BH對應的內存數據塊的內容。
do塊
志先記錄到PGA中,再寫回undo
commit;
爲事務生成一個scn,lgwr將全部餘下的緩存重作日誌條目寫至磁盤,並把scn記錄到在線重作日誌文件中。
事務條目從v$transaction中刪除
v$lock中記錄會話所持有的鎖,所有釋放,等待排隊這些鎖的每一個事務將會被喚醒,繼續完成他們的工做
若是事務修改的某些塊還在緩衝區緩存中,則會以一種快速的模式訪問並「清理」(指清除存儲在數據庫塊首部與鎖相關的信息。
全表掃描時出現單塊讀:當表中有一些塊存在時,全表掃描,會跳過這些塊,這也是在全表掃描時,出現單塊讀的緣由,還有一種狀況是,到區的邊界,也有可能出現單塊讀。欲成佛,先成莫
XID 回滾段,段的文件編號,塊號,行號,使用次數
 

 

 
 
update慢的因素:
常常update的列不宜採用索引(體系結構P237)
儘可能不要使用觸發器,會使update變慢
觸發器中儘可能不要使用before for each row,沒有after for each row 高效(體系結構P238)
 
相關文章
相關標籤/搜索