1. 程式人生 > >四、資料庫鎖機制--行級鎖(悲觀鎖與樂觀鎖)與表級鎖

四、資料庫鎖機制--行級鎖(悲觀鎖與樂觀鎖)與表級鎖

上一章講到了資料庫事務的隔離級別以及併發事務在不同隔離級別下可能帶來的問題和解決思路,感興趣的朋友可以看一下!

直接切入正題:

      從字面上看,行級鎖的作用範圍肯定比表級鎖的作用範圍要小;行級鎖和表級鎖是根據鎖的粒度來區分的,行記錄,表都是資源,鎖是作用在這些資源上的。如果粒度比較小(比如行級鎖),可以增加系統的併發量但需要較大的系統開銷,會影響到效能,出現死鎖,,因為粒度小則操作的鎖的數量會增加;如果作用在表上,粒度大,開銷小,維護的鎖少,不會出現死鎖,但是併發是相當昂貴的,因為鎖定了整個表就限制了其它事務對這個表中其他記錄的訪問。

行級鎖(tx鎖,也叫事務鎖)

一、悲觀鎖:資料庫行級鎖,目的是讓資料被查出來的時候就加上鎖,然後再執行下面的程式邏輯,這樣後面為了操作相同資料而進來的請求,就會在一開始就被攔住(這種效果千萬不要以為可以做防重複提交)

在操作DML(insert,update,delete)語句時,oracle會自動加上行級鎖,在select * from table for update 【of column】【nowait|wait 3】時,oracle也會自動加鎖

  單表 for update

 1. 一般在for update 時加nowait,這樣就不用等待其他事務執行了,一判斷有事務,立馬丟擲錯誤。

下面簡單說一下 for update的四種情況:

1.1    select * from table where id = '1001' for update 鎖住了這條資料,那麼另外一個人對該筆資料進行DML操作或者也執行同樣的for update操作時,會檢測到這筆資料上有行級鎖,那麼就會等待著鎖釋放;

這樣就會出現一個問題:其他的程式如果需要對這筆資料操作,就需要等,至於等多久要看鎖什麼時候釋放!

1.2    select * from table where id = '1001' for update nowait,意思就是如果這筆資料上本身加了鎖,另外一個人去執行這句SQL的時候,發現加了鎖,就會直接丟擲異常(ORA-00054:資源正忙),不會等待這筆資料的鎖釋放。

1.3    select * from table where id = '1001' for update wait 5;意思就是如果這筆資料被鎖住,另外一個人如果執行這句SQL後,會等待5秒,如果5秒後這句SQL還沒有得到這筆資料的鎖,就會丟擲異常(ORA-00054:資源正忙

1.4  我們先執行 A語句:select * from table where id = '1001' for update 把1001加上鎖,然後再執行 B語句:select * from table where id = '1001' and id ='1002' for update;這時候肯定查不出來,因為A已經把B要加鎖的資料鎖了,這樣B聯1002的資料都查不出來

解決方案:skip locked

如果把B語句改為:select * from table where id = '1001' and id ='1002' for update skip locked;意思就是執行的時候如果發現要查詢的資料有鎖,就把加了鎖的資料排除,把剩下的資料加鎖,然後查出來!

上面講到了 for update 的四種方式,實際情況如何選擇呢?

關於NOWAIT(如果一定要用FOR UPDATE,我更建議加上NOWAIT)
    當有LOCK衝突時會提示錯誤並結束STATEMENT而不是在那裡等待(比如:要查的行已經被其它事務鎖了,當前的鎖事務與之衝突,加上nowait,當前的事務會結束會提示錯誤並立即結束 STATEMENT而不再等待).
    WAIT 子句指定等待其他使用者釋放鎖的秒數,防止無限期的等待。
  “使用FOR UPDATE WAIT”子句的優點如下:
  1.防止無限期地等待被鎖定的行;
  2.允許應用程式中對鎖的等待時間進行更多的控制。
  3.對於互動式應用程式非常有用,因為這些使用者不能等待不確定

  4.若使用了skip locked,則可以越過鎖定的行,不會報告由wait n 引發的‘資源忙’異常報告

  關聯表for update

2. 現在大部分業務都是聯表查詢,如果用for update 的話,就會把所有關聯表查詢出來的列所在的行全部加鎖,那這個鎖可就重了,比如:

 select * from t1,t2 where t1.id = t2.id and t1.age = '20' for update;就會把T1和T2兩個表中符合條件的行鎖定;

如果上述SQL我只想對T1表的結果集加鎖,怎麼辦?答案:of column_name

例子:

 select * from t1,t2 where t1.id = t2.id and t1.age = '20' for update of t1.id;

這樣就會只把T1表中的符合條件的行加鎖,T2表中符合條件的行不會加鎖。

PS:如果單表for update of column_name查詢,其實和 for update操作是一樣的!

二、樂觀鎖:這不是資料庫本身的鎖,是利用資料比較結果來當做抽象的鎖;舉個例子就明白:

說明:小明成績錯了,要改成績。班主任能改,年級主任也能改!

程式:

{

//先查出來小明的成績

select t.id,t.result from  T t where t.id='10001';---10001,59

//更新成績,改為60

update T t set t.result =‘60’ where t.id='10001'

and t.result = '59'  //加上這個條件的目的就是為了驗證,資料庫裡10001的成績在此期間有沒有被其他人改過,如果改過,那就更新條數為0(因為找不到符合條件的資料);

PS:沒有找到資料,所以沒更新10001這筆資料,最好是程式返回一個沒有更新到這筆資料的提示,如果不加任何提示,前端就會認為更新成功了!

}

分析:

1.利用資料庫中的資料和已經取出的資料的一致性做為“鎖”,與for update相比,樂觀鎖機制是等到更改資料的時候才去校驗,悲觀鎖是讀取資料就開始做了校驗,從這個角度來看,樂觀鎖是對資料庫沒有額外開銷,那麼效率相對是高的。

2. 需要更改的欄位可以作為樂觀鎖的驗證欄位;或者表裡建立version版本號,每更新一次資料版本號+1;或者加lastupdatedate(最後更新時間),同理:資料更改的同時lastupdatedate也跟著變更!

3.其實樂觀鎖存在一個很致命的問題:

場景: 已上述小明改成績為例,假設班主任改的同時,年級主任也改,兩個請求幾乎同時執行了查詢:

select t.id,t.result from  T t where t.id='10001';---10001,59

都查出來是59分!!

然後幾乎同時執行了改成績,班主任改成60分,年級主任改成了80分,關鍵是還都update到10001了

班主任:update T t set t.result =‘60’ where t.id='10001'

and t.result = '59' ;

年級主任: 

update T t set t.result =‘80’ where t.id='10001'

and t.result = '59' ;

這時候班主任事務先提交,資料庫小明成績改成了60,年級主任事務緊接著提交,小明的成績又從60改成了80,那麼對於班主任來說,他的資料就是更新丟失!!!所以大家使用起來要注意併發的情況!!

表級鎖:

一般指的是表結構共享鎖。是不可對該表進行DDL操作,但是對於該表資料的DML操作不受影響

表級鎖講的比較簡單,有興趣的朋友可以深入探究一下~

本章我們就講到這裡,如果大家有什麼想法可以留言,一起討論指正!

對於資料庫事務,隔離性,併發,鎖的認識我們就講到這裡,下一章我們開始講Spring事務以及它的傳播機制等,歡迎觀看!