資料庫中的悲觀鎖和樂觀鎖

xz43發表於2015-10-19

為了得到最大的效能,一般資料庫都有併發機制,不過帶來的問題就是資料訪問的衝突。為了解決這個問題,大多數資料庫用的方法就是資料的鎖定。

1、悲觀鎖,正如其名,它指的是對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。悲觀鎖的實現,往往依靠資料庫提供的鎖機制(也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料)。

2、樂觀鎖( Optimistic Locking ) 
相對悲觀鎖而言,樂觀鎖機制採取了更加寬鬆的加鎖機制。悲觀鎖大多數情況下依靠資料庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是資料庫效能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。
而樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基於資料版本( Version )記錄機制實現。何謂資料版本?即為資料增加一個版本標識,在基於資料庫表的版本解決方案中,一般是透過為資料庫表增加一個 “version” 欄位來實現。讀取出資料時,將此版本號一同讀出,之後更新時,對此版本號加一。此時,將提交資料的版本資料與資料庫表對應記錄的當前版本資訊進行比對,如果提交的資料版本號大於資料庫表當前版本號,則予以更新,否則認為是過期資料。

考慮下面的情況:如果我們先查詢到資料,然後更新資料,這樣會出現這樣的情況。
A執行緒查詢的時候,B執行緒也在查詢,當A執行緒準備更新的時候,B執行緒先獲得 了更新鎖,將這些行鎖定了。A只能等待B更新完。當B執行緒更新完釋放鎖的時候,A獲得鎖,這時A會識別出欄位已經修改,所以會重新查詢資料,然後開始更新。

 

這在資料庫中本來是沒什麼問題的,但應用程式中會出現一個問題。

假如有一個修改商品資訊的頁面 

A,開啟這個頁面,得到商品資料。有編號 ,名稱 ,價格。 

B,也開啟這個頁面,得到商品資料。有編號 ,名稱 ,價格。 

然後A修改商品的價格,然後點儲存。 

B修改商品的名稱,點儲存。 

這裡有一個問題是,當A儲存修改後的商品價格時,他實際上儲存了原編號,原名稱,修改後價格。 

然後B再點儲存的時候,他實際上儲存了原編號,修改後的名稱,原價格。 

這樣,B的修改就將A的修改覆蓋了,導致A的修改無效。

 

那麼,怎麼解決這種問題呢?

 

這裡有兩種方法解決這個問題,也就是所謂的樂觀鎖和悲觀鎖。

 

悲觀鎖

悲觀鎖是這樣實現的。

當查詢到商品資訊,並試圖更新資料時,在查詢的語句後加上for update nowait

這表示當A從準備更新這個資料(查詢這個資料並顯示到頁面上)開始,這個資料就被鎖定了,當你更新完成,才釋放鎖。

當B也準備更新這個資料時,因為有for update nowait,查詢這個資料時並顯示到頁面上就會出錯。所以不會更新。

這樣就能保證每次只有一個準備更新的連線。從而就保證了資料不會出現丟失更新

 

悲觀鎖假定其他使用者企圖訪問或者改變你正在訪問、更改的物件的機率是很高的,因此在悲觀鎖的環境中,在你開始改變此物件之前就將該物件鎖住,並且直 到你提交了所作的更改之後才釋放鎖。悲觀的缺陷是不論是頁鎖還是行鎖,加鎖的時間可能會很長,這樣可能會長時間的限制其他使用者的訪問,也就是說悲觀鎖的並 發訪問性不好。

也就是說,當我們找到需要更改的物件時,就把這些行鎖定,只能讓我修改。當我修改完成時,再釋放這個鎖,別人才能修改。這樣就能防止我找到需要更改的物件到我開始更新資料這段時間內,其他執行緒獲得鎖先更新資料的情況。

 

樂觀鎖

樂觀鎖是這樣實現的。

每次更新時都和舊版本的資料比較。具體如下:

A,開啟這個頁面,得到商品資料。有編號 ,名稱 ,價格。 

B,也開啟這個頁面,得到商品資料。有編號 ,名稱 ,價格。 

然後A修改商品的價格,然後點儲存。這時更新語句是這樣的 

set 編號 = 原編號 ,名稱 = 原名稱 ,價格 =新價格 where 編號 = 原編號 ,名稱 = 原名稱 ,價格 =原價格 

因為A更新時滿足WHERE後條件,所以更新成功。 

當B修改商品的名稱,點儲存。語句是這樣的 

set 編號 = 原編號 ,名稱 = 新名稱 ,價格 =原價格 where 編號 = 原編號 ,名稱 = 原名稱 ,價格 =原價格 

此時價格已經是新的價格,所以價格(新價格) = 原價格 條件不滿足,所以更新不成功。 

這樣就避免了丟失更新。 

每個資料都和舊資料比較,可能比較麻煩,可以專門建一列,用作版本列。 

當更新一次時,版本列資料加1。這樣然後A修改商品的價格,然後點儲存。這時更新語句是這樣的 

set 編號 = 原編號 ,名稱 = 原名稱 ,價格 =新價格 ,版本 = 版本 + 1 where 編號 = 原編號 ,版本 = 版本  

因為A更新時滿足WHERE後條件,所以更新成功。 

當B修改商品的名稱,點儲存。語句是這樣的 

set 編號 = 原編號 ,名稱 = 新名稱 ,價格 =原價格 ,版本 = 版本 + 1 where 編號 = 原編號 ,版本 = 版本  

此時價格已經是新的版本 ,所以版本 (新版本 ) = 原版本  條件不滿足,所以更新不成功。 

這裡的版本號也可以採用時間戳型別,這樣可以順便看到該行最後更新的時間。


樂觀鎖則認為其他使用者企圖改變你正在更改的物件的機率是很小的,因此樂觀鎖直到你準備提交所作的更改時才將物件鎖住,當你讀取以及改變該物件時並不加鎖。 可見樂觀鎖加鎖的時間要比悲觀鎖短,樂觀鎖可以用較大的鎖粒度獲得較好的併發訪問效能。但是如果第二個使用者恰好在第一個使用者提交更改之前讀取了該物件,那 麼當他完成了自己的更改進行提交時,資料庫就會發現該物件已經變化了,這樣,第二個使用者不得不重新讀取該物件並作出更改。這說明在樂觀鎖環境中,會增加並 發使用者讀取物件的次數。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/9399028/viewspace-1814949/,如需轉載,請註明出處,否則將追究法律責任。

相關文章