實際生產環境裡邊,如果並發量不大,完全可以使用悲觀鎖定的方法,這種方法使用起來非常方便和簡單。但是如果系統的並發非常大的話,悲觀鎖定會帶來非常大的效能問題,所以就要選擇樂觀鎖定的方法。
悲觀鎖假定其他使用者企圖訪問或者改變你正在訪問、更改的對象的機率是很高的,因此在悲觀鎖的環境中,在你開始改變此對象之前就將該對象鎖住,並且直到你提交了所作的更改之後才釋放鎖。悲觀的缺陷是不論是頁鎖還是行鎖,加鎖的時間可能會很長,這樣可能會長時間的限制其他使用者的訪問,也就是說悲觀鎖的並發訪問性不好。
樂觀鎖則認為其他使用者企圖改變你正在更改的對象的機率是很小的,因此樂觀鎖直到你準備提交所作的更改時才將對象鎖住,當你讀取以及改變該對象時並不加鎖。可見樂觀鎖加鎖的時間要比悲觀鎖短,樂觀鎖可以用較大的鎖粒度獲得較好的並發訪問效能。但是如果第二個使用者恰好在第一個使用者提交更改之前讀取了該對象,那麼當他完成了自己的更改進行提交時,資料庫就會發現該對象已經變化了,這樣,第二個使用者不得不重新讀取該對象並作出更改。這說明在樂觀鎖環境中,會增加並發使用者讀取對象的次數。
以版本控制系統為例,來說說兩種最基本的並發性問題。
丟失更新】
小張想修改原始碼裡面的a方法,正在她修改的同時,小李開啟了這個檔案,修改了b方法並且儲存了檔案,等小張修改完成後,儲存檔案,小李所做的修改就被覆蓋了。
不一致的讀】
小張想要知道包裡面一共有多少個類,包分了a,b兩個子包。小張開啟a包,看到了7個類。突然小張接到老婆打來的電話,在小張接電話的時候,小李往a包中加了2個類,b包中加了3個類原先b包中是5個類)。
小張接完電話後再開啟b包,看到了8個類,很自然得出結論:包中一共有15個類。
很遺憾,15個永遠不是正確的答案。在小李修改前,正確答案是127+5),修改後是179+8)。這兩個答案都是正確的,雖然有一個不是當前的。但15不對,因為小張讀取的資料是不一致的。
小結:不一致讀指你要讀取兩種資料,這兩種資料都是正確的,但是在同一時刻兩者並非都正確。
隔離 和 不可變】
在公司專屬應用程式中,解決並發衝突的兩種常用手段是隔離和不可變。
只有當多個活動進程或者線程)同時訪問同一資料時才會引發並發問題。一種很自然的思路就是同一時刻只允許一個活動訪問資料。如果小張開啟了檔案,就不允許其他人開啟,或者其他人只能通過唯讀方式開啟副本,就可以解決這個問題。
隔離能夠有效減少發生錯誤的可能。我們經常見到程式員陷入到並發問題的泥潭裡,每一段代碼寫完都要考慮並發問題,這樣太累了。我們可以利用隔離技術建立出隔離地區,當程式進入隔離地區時不用關心並發問題。好的並發性設計就是創造這樣的一些隔離地區,並保證代碼儘可能的運行在其中。
另一種思路:只有當你需要修改共用的資料時才可能引發並發性問題,所以我們可以將要共用的資料製作為“不可變”的,以避免並發性問題。當然我們不可能將所有的資料都做成不可變的,但如果一些資料是不可變的,對它們進行並行作業時我們就可以放鬆自己的神經了。
開放式並行存取控制、封閉式並行存取控制】
如果資料是可變的,並且無法隔離呢?這種情況下最常用的兩種控制就是開放式並行存取控制和封閉式並行存取控制。
假設小張和小李想要同時修改同一個檔案。如果使用樂觀鎖,倆人都能開啟檔案進行修改,如果小張先提交了內容,沒有問題,他所做的改變會儲存到伺服器上。但小李提交時就會遇到麻煩,版本控制伺服器會檢測出兩種修改的衝突,小李的提交會被具體,並由小李決定該如何處理這種情況對於絕大部分版本控制軟體來說,會讀取並標識出小張做的改變,然後由小李決定是否合并)。
如果使用的是悲觀鎖,小張先檢出check out)檔案,那麼小李就無法再次檢出同一檔案,直到小張提交了他的改變。
建議你將樂觀鎖想成一種檢測衝突的手段,而悲觀鎖是一種避免衝突的手段嚴格來說,樂觀鎖其實不能稱之為“鎖”,但是這個名字已經流傳開了,那就繼續使用吧)。一些老的版本控制系統,比如VSS 6.0使用的是悲觀鎖的機制。而現代的版本控制系統一般兩種都支援,預設使用樂觀鎖。
兩種鎖各有優缺點。。。這段懶的翻譯了,很明顯看出,樂觀鎖可以提高並發訪問的效率,但是如果出現了衝突只能向上拋出,然後重來一遍;悲觀鎖可以避免衝突的發生,但是會降低效率。
選擇使用那一種鎖取決於訪問頻率和一旦產生衝突的嚴重性。如果系統被並發訪問的機率很低,或者衝突發生後的後果不太嚴重所謂後果應該指被檢測到衝突的提交會失敗,必須重來一次),可以使用樂觀鎖,否則使用悲觀鎖。
我再補充兩句】我們經常會在訪問資料庫的時候用到鎖,怎麼實現樂觀鎖和悲觀鎖呢?以Hibernate為例,可以通過為記錄添加版本或時間戳記欄位來實現樂觀鎖。可以用session.Lock()鎖定對象來實現悲觀鎖本質上就是執行了SELECT * FROM t FOR UPDATE語句)。
另一個高並發控制解決方案開放式並行存取控制和封閉式並行存取控制總結概述:
我們可以使用兩種形式的並發控制策略:開放式並行存取控制和封閉式並行存取控制。
假設martin和David同時都要編輯Customer檔案。如果使用樂觀鎖策略,他們兩個人都能得到一份檔案的Copy,並且
可以自由編輯檔案。假設David第一個完成了工作,那麼他可以毫無困難地更新他的修改。但是,當Martin想要提交他的修改時,並發控制策略就會開始起作用。原始碼控制系統會檢測到在Martin的修改與David的修改之間存在著衝突,因而拒絕Martin的提交,並由Martin負責指出怎樣處理這種情況。如果使用悲觀鎖策略,只要有人先取出檔案,其他人就不能對該檔案進行編輯。因此,假如是Martin先取了檔案,那麼David就只能在Martin完成任務並提交之後才能對該檔案進行操作。
如果把樂觀鎖看作是關於衝突檢測的,那麼悲觀鎖就是關於衝突避免的。在實際應用的原始碼控制系統中,
這兩種策略都可以被使用,但是現在大多數原始碼開發人員更傾向於使用樂觀鎖策略。有一種很有道理的說法:樂觀鎖並不是真正的鎖定,但是這種叫法很方便並且廣泛流傳,以至於不容忽略。)
這兩種策略各有優缺點。悲觀鎖的問題是減少了並發的程式。當Martin正對一個被他加鎖的檔案進行編輯的時候,
其它人只能等著。使用過悲觀的原始碼控制人都知道這是一種多麼令人喪氣的事情。對於企業資料,情況經常會變得更加糟糕,只要有人在編輯,其他人就無法進行讀取,更加說進行編輯了。
樂觀鎖策略則允許人們更自由一些,因為只有在提交的時候才有可能遇到阻礙。該策略的問題在於當衝突的時候會發生什麼樣的事情呢?事實上,David之後的所有人在提交的時候都必須讀取David修改過的那個版本,並指出怎樣合并自己和David的修改,然後再提交一個重新修改過的最新版本。有了原始碼控制系統,這樣做並不會有什麼麻煩。在許多場合下,原始碼控制系統確實能夠自動進行合併作業,甚至在無法自動合并的時候,也能讓使用都很容易看出不同檔案版本之間的差別。但是,業務資料通常都是很難被自動合并的,所以經常只能扔掉原來的東西,然後從頭開始。
在樂觀鎖和悲觀鎖之間進行選擇的標準是:衝突的頻率與嚴重性。如果衝突很少,或者衝突的後果不會很嚴重,那麼通常情況下應該選擇樂觀鎖,因為它能得到更好的並發性,而且更容易實現。但是,如果衝突的結果對於使用者來說痛苦的,那麼就需要使用悲觀策略。
樂觀鎖的局限是:只能在提交資料時才發現業務事務將要失敗,而且在某些情況下,發現失敗太遲的代價會很大。使用者可能花了一個小時的時間輸入一份租約的詳細資料,錯誤太多會讓使用者對系統失去信心。另一個方法是使用悲觀鎖,它可以儘早地發現錯誤,但理難以編程實現,而且會降低系統的靈活性。
註:以上是對並發控制中的樂觀鎖策略和悲觀鎖策略概念及解決思路的文字描述,下面我將對項目中具體怎麼實現樂觀鎖策略及悲觀鎖策略進行描述。)
樂觀鎖策略實現方法:
就是用C#中或SQL中的事務來實現資料操作不成功就復原,個人感覺火車站賣票系統也是這樣操作的,我們看到顯示屏上有少量剩餘票,但我們去買又打不出來。
悲觀鎖策略實現方法:
1、普通的aspx頁面,當使用者點提交後,直接將提交及相關按鈕的enabel改為false,直到提交事件完成後,再改回來。另外在資料層那一塊,每次提交資料更改時,都需要判斷資料以前的狀態是否改變,以防止有並發改變的情況出現。
2、jquery中,在jquery中,可以設定一個全域變數,提交時,先判斷全域變數狀態,如不允許提交則直接返回,如允許提交時,則先將全域變數置為“不允許提交”,後開始提交,提交完成後,在jquery的post方法的callback方法中,再將全域變數改為“允許提交”。
3、彈出式視窗修改頁面,則用模態方式彈出,如web頁面中,可用window.showModalDialog()來實現模態方式開啟修改頁面,來確保始終只有一個修改頁面被開啟。這是從資料操作頁面處就悲觀鎖定了資料,而不是在資料庫裡面悲觀鎖定)
原文標題:在資料庫中,並發控制有樂觀鎖和悲觀鎖之間,什麼時候用樂觀鎖比較好什麼時候用悲觀鎖比較好?
連結:http://www.cnblogs.com/chenlulouis/archive/2010/08/17/1801358.html