先申明概念:
1、悲觀鎖,正如其名,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機制(也只有數(shù)據(jù)庫層提供的鎖機制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實現(xiàn)了加鎖機制,也無法保證外部系統(tǒng)不會修改數(shù)據(jù))。
2、樂觀鎖( Optimistic Locking )
相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機制實現(xiàn),以保證操作最大程度的獨占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對長事務(wù)而言,這樣的開銷往往無法承受。而樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基于數(shù)據(jù)版本( Version )記錄機制實現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個版本標識,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個 “version” 字段來實現(xiàn)。讀取出數(shù)據(jù)時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應(yīng)記錄的當前版本信息進行比對,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫表當前版本號,則予以更新,否則認為是過期數(shù)據(jù)。
所以悲觀鎖和樂觀鎖最大的區(qū)別是是否一直鎖定資源,悲觀鎖在事物的全流程鎖定數(shù)據(jù),樂觀鎖不鎖定數(shù)據(jù)(用讀寫鎖是阻塞事物,而用樂觀鎖則會導(dǎo)致回滾。這個是一種事物沖突后的不同鎖的表象)。樂觀鎖的最大特點是在最后檢查數(shù)據(jù)是否被修改,如果已被別人修改過,則回滾數(shù)據(jù),避免臟數(shù)據(jù)。至于事物是否沖突和加鎖沒有直接聯(lián)系,該沖突的還是會沖突,不管你加悲觀鎖和樂觀鎖都會沖突。
悲觀鎖和樂觀鎖都是為了解決丟失更新問題或者是臟讀。悲觀鎖和樂觀鎖的重點就是是否在讀取記錄的時候直接上鎖。悲觀鎖的缺點很明顯,需要一個持續(xù)的數(shù)據(jù)庫連接,這在web應(yīng)用中已經(jīng)不適合了。
一個比較清楚的場景
下面這個假設(shè)的實際場景可以比較清楚的幫助我們理解這個問題:
a. 假設(shè)當當網(wǎng)上用戶下單買了本書,這時數(shù)據(jù)庫中有條訂單號為001的訂單,其中有個status字段是’有效’,表示該訂單是有效的;
b. 后臺管理人員查詢到這條001的訂單,并且看到狀態(tài)是有效的
c. 用戶發(fā)現(xiàn)下單的時候下錯了,于是撤銷訂單,假設(shè)運行這樣一條SQL: update order_table set status = ‘取消’ where order_id = 001;
d. 后臺管理人員由于在b這步看到狀態(tài)有效的,這時,雖然用戶在c這步已經(jīng)撤銷了訂單,可是管理人員并未刷新界面,看到的訂單狀態(tài)還是有效的,于是點擊”發(fā)貨”按鈕,將該訂單發(fā)到物流部門,同時運行類似如下SQL,將訂單狀態(tài)改成已發(fā)貨:update order_table set status = ‘已發(fā)貨’ where order_id = 001
觀點1:只有沖突非常嚴重的系統(tǒng)才需要悲觀鎖;
分析:這是更準確的說法;
“所有悲觀鎖的做法都適合于狀態(tài)被修改的概率比較高的情況,具體是否合適則需要根據(jù)實際情況判斷。”,表達的也是這個意思,不過說法不夠準確;的確,之所以用悲觀鎖就是因為兩個用戶更新同一條數(shù)據(jù)的概率高,也就是沖突比較嚴重的情況下,所以才用悲觀鎖。
觀點2:最后提交前作一次select for update檢查,然后再提交update也是一種樂觀鎖的做法
分析:這是更準確的說法;
的確,這符合傳統(tǒng)樂觀鎖的做法,就是到最后再去檢查。但是wiki在解釋悲觀鎖的做法的時候,’It is not appropriate for use in web application development.’, 現(xiàn)在已經(jīng)很少有悲觀鎖的做法了,所以我自己將這種二次檢查的做法也歸為悲觀鎖的變種,因為這在所有樂觀鎖里面,做法和悲觀鎖是最接近的,都是先select for update,然后update
*****除了上面的觀點1和觀點2是更準確的說法,下面的所有觀點都是錯誤的***********
觀點3:這個問題的原因是因為數(shù)據(jù)庫隔離級別是uncommitted read級別;
分析:這個觀點是錯誤的;
這個過程本身就是在read committed隔離級別下發(fā)生的,從a到d每一步,尤其是d這步,并不是因為讀到了未提交的數(shù)據(jù),僅僅是因為用戶界面沒有刷新[事實上也不可能做自動刷新,這樣相當于數(shù)據(jù)庫一發(fā)生改變立刻要刷新了,這需要監(jiān)聽數(shù)據(jù)庫了,顯然這是簡單問題復(fù)雜化了];
觀點4:悲觀鎖是指一個用戶在更新數(shù)據(jù)的時候,其他用戶不能讀取這條記錄;也就是update阻塞讀才叫悲觀鎖;
分析:這個觀點是錯的;
這在db2背景的開發(fā)中尤其常見;因為db2默認就是update會阻塞讀;但是這是各個數(shù)據(jù)庫對讀寫的時候上鎖的并發(fā)處理實現(xiàn)不一樣。但這根本不是悲觀鎖樂觀鎖的區(qū)別。Oracle可以做到寫不阻塞讀僅僅是因為做了多版本并發(fā)控制(Multiversion concurrency control), http://en.wikipedia.org/wiki/Multiversion_concurrency_control;但是在Oracle里面,一樣可以做樂觀鎖和悲觀鎖的控制。這本質(zhì)上是應(yīng)用層面的選擇。
觀點5:Oracle實際上用的就是樂觀鎖
分析:這個觀點是錯的;
前面說了,Oracle的確可以做到寫不阻塞讀,但是這不是悲觀鎖和樂觀鎖的問題。這是因為實現(xiàn)了多版本并發(fā)控制。按照wiki的定義,悲觀鎖和樂觀鎖是在應(yīng)用層面選擇的。Oracle的應(yīng)用只要在第二步做了select for update,就是悲觀鎖的做法;況且Oracle在任何隔離級別下,除了分布式事務(wù)兩階段提交的短暫時間,其他所有情況下都不存在寫阻塞讀的情況,如果按照這個觀點的話那Oracle已經(jīng)不能做悲觀鎖了-_-
觀點6:不需要這么麻煩,只需要在d這步,最后提交更新的時候再做一個普通的select檢查一下就可以;[就是double check的做法]
分析:這個觀點是錯的。
這個做法其實在http://www.hetaoblog.com/database-lost-update-pessimistic-lock/,’3. 傳統(tǒng)悲觀鎖做法的變通’這節(jié)已經(jīng)說明了,如果要這么做的話,仍然需要在最后提交更新前double check的時候做一個select for update, 否則select結(jié)束到update提交前的時間仍然有可能記錄被修改;
觀點7:應(yīng)該盡可能使用悲觀鎖;
分析:這個觀點是錯的;
a. 根據(jù)悲觀鎖的概念,用戶在讀的時候(b這步)就會將記錄鎖住,直到更新結(jié)束的時候才會將鎖釋放,所以整個鎖的過程時間比較長;
b. 另外,悲觀鎖需要有一個持續(xù)的數(shù)據(jù)庫連接,這在當今的web應(yīng)用中已經(jīng)幾乎不存在;wiki上也說了, 悲觀鎖‘is not appropriate for use in web application development.’
所以,現(xiàn)在大部分應(yīng)用都應(yīng)該是樂觀鎖的;
轉(zhuǎn)自:http://zhidao.baidu.com/link?url=MUOUg59oz7-FKwz-zuUviGryfw9J4V63Pd2iWWErorwUpyeL85rznlmYaGDHXjH_ChywA3R1m9XNpx4k7RCCT3rNofjkCxIBYHdsvwr2bVy