天天看點

使用悲觀鎖還是樂觀鎖

一、悲觀鎖和樂觀鎖

讀取頻繁使用樂觀鎖,寫入頻繁使用悲觀鎖。

樂觀鎖想成一種檢測沖突的手段,而悲觀鎖是一種避免沖突的手段。

1. 悲觀鎖(Pessimistic Lock)

​悲觀鎖​:假定會發生并發沖突,屏蔽一切可能違反資料完整性的操作。

實際生産環境裡邊,如果并發量不大,完全可以使用悲觀鎖定的方法,這種方法使用起來非常友善和簡單。但是如果系統的并發非常大的話,悲觀鎖定會帶來非常大的性能問題,是以就要選擇樂觀鎖定的方法。

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

2. 樂觀鎖(Optimistic Lock)

​樂觀鎖​:假設不會發生并發沖突,隻在送出操作時檢查是否違反資料完整性。 樂觀鎖不能解決髒讀的問題。

樂觀鎖則認為其他使用者企圖改變你正在更改的對象的機率是很小的,是以樂觀鎖直到你準備送出所作的更改時才将對象鎖住,當你讀取以及改變該對象時并不加鎖。可見樂觀鎖加鎖的時間要比悲觀鎖短,樂觀鎖可以用較大的鎖粒度獲得較好的并發通路性能。

但是如果第二個使用者恰好在第一個使用者送出更改之前讀取了該對象,那麼當他完成了自己的更改進行送出時,資料庫就會發現該對象已經變化了,這樣,第二個使用者不得不重新讀取該對象并作出更改。這說明在樂觀鎖環境中,會增加并發使用者讀取對象的次數。

以版本控制系統為例,來說說兩種最基本的并發性問題。

二、沖突導緻的并發性問題

​1. 丢失更新:​

​寫-寫沖突​

一個事務的更新覆寫了其它事務的更新結果

小張想修改源代碼裡面的a方法。

正在她修改的同時,小李打開了這個檔案,修改了b方法并且儲存了檔案。

等小張修改完成後,儲存檔案,小李所做的修改就被覆寫了。

​2. 不一緻的讀(髒讀):​

​寫-讀沖突​

當一個事務讀取其它完成一半事務的記錄時,就會發生髒讀取。

小張想要知道包裡面一共有多少個類,包分了a,b兩個子包。

小張打開a包,看到了7個類。

突然小張接到老婆打來的電話,在小張接電話的時候,小李往a包中加了2個類,b包中加了3個類(原先b包中是5個類)。

小張接完電話後再打開b包,看到了8個類,很自然得出結論:包中一共有15個類。

很遺憾,15個永遠不是正确的答案。

在小李修改前,正确答案是12(7+5),修改後是17(9+8)。

這兩個答案都是正确的,雖然有一個不是目前的。但15不對,因為小張讀取的資料是不一緻的。

小結:不一緻讀指你要讀取兩種資料,這兩種資料都是正确的,但是在同一時刻兩者并非都正确。

三、隔離和不可變:

在企業應用中,解決并發沖突的兩種常用手段是隔離和不可變。

隻有當多個活動(程序或者線程)同時通路同一資料時才會引發并發問題。一種很自然的思路就是同一時刻隻允許一個活動通路資料。如果小張打開了檔案,就不允許其他人打開,或者其他人隻能通過隻讀的方式打開副本,就可以解決這個問題。

隔離能夠有效減少發生錯誤的可能。我們經常見到程式員陷入到并發問題的泥潭裡,每一段代碼寫完都要考慮并發問題,這樣太累了。我們可以利用隔離技術建立出隔離區域,當程式進入隔離區域時不用關心并發問題。好的并發性設計就是創造這樣的一些隔離區域,并保證代碼盡可能的運作在其中。

​另一種思路:​隻有當你需要修改共享的資料時才可能引發并發性問題,是以我們可以将要共享的資料制作為“不可變”的,以避免并發性問題。當然我們不可能将所有的資料都做成不可變的,但如果一些資料是不可變的,對它們進行并發操作時我們就可以放松自己的神經了。

四、樂觀并發控制、悲觀并發控制:

如果資料是可變的,并且無法隔離呢?這種情況下最常用的兩種控制就是樂觀并發控制和悲觀并發控制。

假設小張和小李想要同時修改同一個檔案。

如果使用樂觀鎖,倆人都能打開檔案進行修改,如果小張先送出了内容,沒有問題,他所做的改變會儲存到伺服器上。但小李送出時就會遇到麻煩,版本控制伺服器會檢測出兩種修改的沖突,小李的送出會被拒絕,并由小李決定該如何處理這種情況(對于絕大部分版本控制軟體來說,會讀取并辨別出小張做的改變,然後由小李決定是否合并)。

如果使用的是悲觀鎖,小張先檢出(check out)檔案,那麼小李就無法再次檢出同一檔案,直到小張送出了他的改變。

建議你将樂觀鎖想成一種檢測沖突的手段,而悲觀鎖是一種避免沖突的手段(嚴格來說,樂觀鎖其實不能稱之為“鎖”,但是這個名字已經流傳開了,那就繼續使用吧)。

兩種鎖各有優缺點。

樂觀鎖可以提高并發通路的效率,但是如果出現了沖突隻能向上抛出,然後重來一遍;悲觀鎖可以避免沖突的發生,但是會降低效率。

選擇使用哪一種鎖取決于通路頻率和一旦産生沖突的嚴重性。如果系統被并發通路的機率很低,或者沖突發生後的後果不太嚴重(所謂後果應該指被檢測到沖突的送出會失敗,必須重來一次),可以使用樂觀鎖,否則使用悲觀鎖。

我們可以使用兩種形式的并發控制政策:樂觀并發控制和悲觀并發控制。

在實際應用的源代碼控制系統中,這兩種政策都可以被使用,但是現在大多數源代碼開發者更傾向于使用樂觀鎖政策。

這兩種政策各有優缺點。悲觀鎖的問題是減少了并發的程式。

在樂觀鎖和悲觀鎖之間進行選擇的标準是:沖突的頻率與嚴重性。

如果沖突很少,或者沖突的後果不會很嚴重,那麼通常情況下應該選擇樂觀鎖,因為它能得到更好的并發性,而且更容易實作。

但是,如果沖突的結果對于使用者來說痛苦的,那麼就需要使用悲觀政策。

​樂觀鎖的局限是:​

隻能在送出資料時才發現業務事務将要失敗,而且在某些情況下,發現失敗太遲的代價會很大。使用者可能花了一個小時的時間輸入一份租約的詳細資訊,錯誤太多會讓使用者對系統失去信心。另一個方法是使用悲觀鎖,它可以盡早地發現錯誤,但理難以程式設計實作,而且會降低系統的靈活性。

注:以上是對并發控制中的樂觀鎖政策和悲觀鎖政策概念及解決思路的文字描述,下面我将對項目中具體怎麼實作樂觀鎖政策及悲觀鎖政策進行描述。

樂觀鎖應用

  1. 使用自增長的整數表示資料版本号。更新時檢查版本号是否一緻,比如資料庫中資料版本為6,更新送出時version=6+1,使用該version值(=7)與資料庫version+1(=7)作比較,如果相等,則可以更新,如果不等則有可能其他程式已更新該記錄,是以傳回錯誤。
  2. 使用時間戳來實作.

悲觀鎖應用

需要使用資料庫的鎖機制,比如SQL SERVER 的TABLOCKX(排它表鎖) 此選項被選中時,SQL Server 将在整個表上置排它鎖直至該指令或事務結束。這将防止其他程序讀取或修改表中的資料。

在實際生産環境裡邊,如果并發量不大且不允許髒讀,可以使用悲觀鎖解決并發問題;

但如果系統的并發非常大的話,悲觀鎖定會帶來非常大的性能問題,是以我們就要選擇樂觀鎖定的方法.

悲觀鎖會造成通路資料庫時間較長,并發性不好,特别是長事務。