數據庫事務隔離級別和鎖實現機制

一 數據庫事務處理中出現的數據不一致的情況 

在多個事務併發做數據庫操作的時候,如果沒有有效的避免機制,就會出現種種問題。大體上有四種問題,歸結如下:


1、丟失更新  
如果兩個事務都要更新數據庫一個字段X,x=100

事務A 事務B
讀取X=100  讀取X=100
寫入x=X+100 寫入x=X+200
事務結束x=200  事務結束x=300
  最後x=300

 

 

 

 

 

 

 

 

兩個不同事物同時獲得相同數據,然後在各自事務中同時修改了該數據,那麼先提交的事務更新會被後提交事務的更新給覆蓋掉,這種情況事務A的更新就被覆蓋掉了、丟失了。

2、髒讀(未提交讀) 
防止一個事務讀到另一個事務還沒有提交的記錄。 如:

事務A    事務B
  寫入x=X+100 (x=200)
讀取X=200 (讀取了事務B未提交的數據)    
  事務回滾x=100 
  事務結束x=100
事務結束  

              
       

  

 

 

 

 

 

事務讀取了未提交的數據,事務B的回滾,導致了事務A的數據不一致,導致了事務A的髒讀 !

3、不可重複讀
一個事務在自己沒有更新數據庫數據的情況,同一個查詢操作執行兩次或多次的結果應該是一致的;如果不一致,就說明爲不可重複讀。
還是用上面的例子

事務A 事務B
讀取X=100 讀取X=100
讀取X=100  寫入x=X+100

事務結束, x=200
讀取X=200
(此時,在同一個事務A中,讀取的X值發生了變化!)
 
事務結束  

 

 

 

 

 

 

 

 


這種情況事務A多次讀取x的結果出現了不一致,即爲不可重複讀 。

4 幻讀(Phantom Read)

事務A讀的時候讀出了15條記錄,事務B在事務A執行的過程中 增加 了1條,事務A再讀的時候就變成了 16 條,這種情況就叫做幻影讀。
不可重複讀說明了做數據庫讀操作的時候可能會出現的問題。

二 事務隔離級別通過鎖的實現機制

兩個鎖:

排他鎖 被加鎖的對象只能被持有鎖的事務讀取和修改,其他事務無法在該對象上加其他鎖,也不能讀取和修改該對象
共享鎖 被加鎖的對象可以被持鎖事務讀取,但是不能被修改,其他事務也可以在上面再加共享鎖。

 

特別的,對共享鎖: 如果兩個事務對同一個資源上了共享鎖,事務A 想更新該數據,那麼它必須等待 事務B 釋放其共享鎖。

在運用 排他鎖 和 共享鎖 對數據對象加鎖時,還需要約定一些規則,例如何時申請 排他鎖 或 共享鎖、持鎖時間、何時釋放等。稱這些規則爲封鎖協議(Locking Protocol)。對封鎖方式規定不同的規則,就形成了各種不同的封鎖協議。

1、一級封鎖協議 (對應 read uncommited)   
     一級封鎖協議是:事務 在對需要修改的數據上面(就是在發生修改的瞬間) 對其加共享鎖(其他事務不能更改,但是可以讀取-導致“髒讀”),直到事務結束才釋放。事務結束包括正常結束(COMMIT)和非正常結束(ROLLBACK)。
     一級封鎖協議不能避免 丟失更新,髒讀,不可重複讀,幻讀!

2、二級封鎖協議 (對應read commited) 
     二級封鎖協議是:1)事務 在對需要更新的數據 上(就是發生更新的瞬間) 加 排他鎖 (直到事務結束) , 防止其他事務讀取未提交的數據,這樣,也就避免了 “髒讀” 的情況。2)事務 對當前被讀取的數據 上面加共享鎖(當讀到時加上共享鎖),一旦讀完該行,立即 釋放該 該行的共享鎖 - 從數據庫的底層實現更深入的來理解,既是,數據庫會對遊標當前的數據上加共享鎖 , 但是當遊標離開當前行的時候,立即釋放該行的共享鎖。 
     二級封鎖協議除防止了“髒讀”數據,但是不能避免 丟失更新,不可重複讀,幻讀 。

     但在二級封鎖協議中,由於讀完數據後立即 釋放共享鎖,所以它不能避免可重複讀 ,同時它也不能避免 丟失更新 ,如果事務A、B同時獲取資源X,然後事務A先發起更新記錄X,那麼 事務B 將等待事務 A 執行完成,然後獲得記錄X 的排他鎖,進行更改。這樣事務 A 的更新將會被丟失。 具體情況如下:

 

事務A 事務B
讀取X=100(同時上共享鎖) 讀取X=100(同時上共享鎖)
讀取成功(釋放共享鎖) 讀取成功(釋放共享鎖)
UPDATE X=X+100 (上排他鎖)  
  UPDATING A(等待事務A釋放對X的排他鎖)
事務成功(釋放排他鎖)X=200  
  UPDATE X=X+200(成功上排他鎖)
  事務成功(釋放排他鎖)X=300

 

由此可以看到,事務A的提交被事務B覆蓋了,所以不能防止 丟失更新。
如果要避免 丟失更新,我們需要額外的操作, 對凡是讀到的數據加 共享鎖 排他鎖 ,這個往往需要程序員自己編程實現,比如在Oracle 中,需要加 SELECT FOR UPDATE 語句,表明,凡是該事務讀到的數據,額外的加上排他鎖,防止其他數據同一時間獲取相同數據,這樣就防止了 丟失更新 ! 

3、三級封鎖協議 (對應reapetable read )
      三級封鎖協議是:二級封鎖協議加上事務 在讀取數據的瞬間 必須先對其加 共享鎖 ,但是 直到事務結束才釋放 ,這樣保證了可重複讀(既是其他的事務職能讀取該數據,但是不能更新該數據)。 
      三級封鎖協議除防止了“髒”數據 和不可重複讀 。但是這種情況不能避免 幻讀 和 丟失更新 的情況,在事務 A 沒有完成之前,事務 B 可以新增數據,那麼 當事務 A 再次讀取的時候,事務B 新增的數據會被讀取到,這樣,在該封鎖協議下,幻讀 就產生了。 如果事務A 和 事務B 同時讀取了資源X=100,同樣,如果事務A先對X進行 更新X=X+100,等待事務A執行完成X=200,那麼事務B 獲得X的排他鎖,進行更新 X=X+200,然後提交 X=300,同樣A的更新被B所覆蓋!( 如果要避免 丟失更新,我們需要額外的操作, 對凡是讀到的數據加 共享鎖 排他鎖 ,這個往往需要程序員自己編程實現,比如在Oracle 中,需要加 SELECT FOR UPDATE 語句,表明,凡是讀到的數據,我會加 排他鎖,防止其他數據同一時間獲取相同數據) ! 

      進階:repeatable read 導致死鎖的情況(即便是 不同的資源在相同的順序下獲取)。 比如 事務1 讀取 A,同時 事務2 也讀取 A,那麼事務1和事務2 同時對 A 上了共享鎖,然後事務1 要UPDATE A,而此時 事務2 也要 UPDATE A,這個時候 事務1 等待 事務2 釋放其在 A 上的共享鎖,然後 事務2 要等待 事務1 釋放其在 A 上的共享鎖,這樣,事務1 和 事務2 相互等待,產生死鎖!(SQL Server/DB2 裏面有 UPDATE LOCK 可以解決這種情況,具體的思路是,在 repeatable read 的情況下,將讀取的數據 上的 UPDATE 鎖,介於 共享鎖 和 排他鎖之間的一種鎖,該鎖的作用是 當出現上面這種情況後,事務1 和 事務2 對 A 上的是 UPDATE 鎖,那麼誰先 要修改 A,那麼該事務就會將 UPDATE 鎖可以順利升級爲 排他鎖對該數據進行修改!)

 

4、最強封鎖協議(對應Serialization)

      四級封鎖協議是對三級封鎖協議的增強,其實現機制也最爲簡單,直接對 事務中 所 讀取 或者 更改的數據所在的表加表鎖,也就是說,其他事務不能 讀寫 該表中的任何數據。這樣所有的 髒讀,不可重複讀,幻讀 ,都得以避免!

 

三 附Oracle 事務一致性原則
        事務是定義和維護一致性的單位,封鎖就是要保證這種一致性。如果   
對封鎖的要求高會增加開銷,降低併發性和效率;有的事務並不嚴格要求   
結果的質量(如用於統計的事務),如果加上嚴格的封鎖則是不必要和不   
經濟的。因此有必要進行進一步的分析,考察不同級別的一致性對數據庫   
數據的質量及並行能力的影響。   
        一致性級別定義爲如下的幾個條件:   
    (1)   事務不修改其它任何事務的髒數據。髒數據是被其它事務修改過,   
但尚未提交的數據。   
    (2)   在事務結束前不對被修改的資源解鎖。   
    (3)   事務不讀其它任何事務的髒數據。   
    (4)   在讀前對數據加共享鎖(RS)和行排它鎖,直至事務結束。   
            *   滿足條件1的事務叫第0級事務。   
            *   滿足條件1和2的事務叫第1級一致性事務。   
            *   滿足條件1、2和3的事務爲2級一致性事務。ORACLE的讀一致性保   
證了事務不讀其它事務的髒數據。   
            *   滿足條件1、2、3和4的事務叫第3級一致性事務。   
        由ORACLE的三個性質:自動加隱式鎖、在事務結束時釋放鎖和讀一致   
性,使ORACLE成爲自動滿足以上的0、1和2級一致性事務。因此,ORACLE   
自動防止了髒讀(寫-讀依賴)。但是,ORACLE不能自動防止丟失修改(寫   
-寫依賴),讀的不可重複性(讀-寫依賴),徹底解決併發性中的問題還   
需滿足第4個條件(3級一致性事務),這需要程序員根據實際情況編程。   
方法如下: 
        *   如果想在一段時間內使一些數據不被其它事務改變,且在本事務內   
            僅僅查詢數據,則可用SET   TRANSACTION   READ   ONLY   語句達到這一   
            目的。   
        *   如果想在一事務內修改一數據,且避免丟失修改,則應在讀這一數   
            據前用SELECT   FOR   UPDATE對該數據加鎖。   
        *   如果想在一事務內讀一數據,且想基於這一數據對其它數據修改,   
            則應在讀數據前對此數據用SELECT   FOR   UPDATE加鎖。對此種類型   
            的應用,用這條SQL語句加鎖最恰當。   
        *   如果想避免不可重複讀現象,可在讀前用SELECT   FOR   UPDATE對數   
            據加鎖,或用SET   TRANSACTION   READ   ONLY設置只讀事務。  


四, 特殊情況 
1) Read-Commit 的行鎖導致其他事務一直被 hanging on的情況!

假設我們有 VARIANT 表, Trasaction A 對 Variant 中的字段 VariantName 1 進行了修改,但是事務未提交(假設,該事務將執行1個小時),此時 Trasaction B 要讀VariantName(查詢某一個VariantName 2 ),此時它會一直被Transaction A 阻塞,直到Transaction A 提交對 VariantName 的修改後,Transaction B纔會得到VariantName 2 的查詢結果,這樣Transaction B最長可被阻塞1個小時!

這裏,雖然Transaction A是針對 VariantName 1 上的修改,而 Transaction B 是讀取 VariantName 2 , 對應的Variant Name不一樣,但是此時,Transaction B並不知道 Transaction A 的結果(對Transaction B而言,它不清楚Transaction A提交的結果是什麼),爲了避免“髒讀”,Transaction B會等待 Transaction A執行完事務以後,完成它對VariantName的修改後,才返回結果!

 

所以,在一個事務中,我們應該儘量把 SELECT Queries 放到最前面,把所有的 Update 放到最後面,避免不必要的等待!

 

特別的,如果上面這種情況,VariantName是Unique Index或者是Primary Key, 這個時候,Transaction B不會被Transaction A 阻塞!因爲 Transaction B 知道 Transaction A提交的更改不會影響 他獲取的VariantName 2 因爲Transaction B 知道 VariantName 2 是唯一的!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章