每次寫博客,第一句話都是這樣的:程序員很苦逼,除了會寫程序,還得會寫博客!固然,但願未來的一天,某位老闆看到此博客,給你的程序員職工加點薪資吧!由於程序員的世界除了苦逼就是沉默。我眼中的程序員大多都不愛說話,默默承受着編程的巨大壓力,除了技術上的交流外,他們不肯意也不擅長和別人交流,更不樂意任何人走進他們的心裏!html
最近悟出來一個道理,在這兒分享給你們:學歷表明你的過去,能力表明你的如今,學習表明你的未來。咱們都知道計算機技術發展突飛猛進,速度驚人的快,你我稍不留神,就會被慢慢淘汰!所以:每日不間斷的學習是避免被淘汰的不二法寶。程序員
固然,題外話說多了,咱進入正題!sql
在多用戶環境中,在同一時間可能會有多個用戶更新相同的記錄,這會產生衝突。這就是著名的併發性問題。數據庫
典型的衝突有:編程
- 丟失更新:一個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如:用戶A把值從6改成2,用戶B把值從2改成6,則用戶A丟失了他的更新。
- 髒讀:當一個事務讀取其它完成一半事務的記錄時,就會發生髒讀取。例如:用戶A,B看到的值都是6,用戶B把值改成2,用戶A讀到的值仍爲6。
爲了解決這些併發帶來的問題。 咱們須要引入併發控制機制。併發
併發控制機制
悲觀鎖:假定會發生併發衝突,屏蔽一切可能違反數據完整性的操做。[1]編程語言
樂觀鎖:假設不會發生併發衝突,只在提交操做時檢查是否違反數據完整性。[1] 樂觀鎖不能解決髒讀的問題。post
最經常使用的處理多用戶併發訪問的方法是加鎖。當一個用戶鎖住數據庫中的某個對象時,其餘用戶就不能再訪問該對象。加鎖對併發訪問的影響體如今鎖的粒度上。好比,放在一個表上的鎖限制對整個表的併發訪問;放在數據頁上的鎖限制了對整個數據頁的訪問;放在行上的鎖只限制對該行的併發訪問。可見行鎖粒度最小,併發訪問最好,頁鎖粒度最大,併發訪問性能就會越低。性能
悲觀鎖:假定會發生併發衝突,屏蔽一切可能違反數據完整性的操做。[1] 悲觀鎖假定其餘用戶企圖訪問或者改變你正在訪問、更改的對象的機率是很高的,所以在悲觀鎖的環境中,在你開始改變此對象以前就將該對象鎖住,而且直到你提交了所做的更改以後才釋放鎖。悲觀的缺陷是不管是頁鎖仍是行鎖,加鎖的時間可能會很長,這樣可能會長時間的鎖定一個對象,限制其餘用戶的訪問,也就是說悲觀鎖的併發訪問性很差。學習
樂觀鎖:假設不會發生併發衝突,只在提交操做時檢查是否違反數據完整性。[1] 樂觀鎖不能解決髒讀的問題。 樂觀鎖則認爲其餘用戶企圖改變你正在更改的對象的機率是很小的,所以樂觀鎖直到你準備提交所做的更改時纔將對象鎖住,當你讀取以及改變該對象時並不加鎖。可見樂觀鎖加鎖的時間要比悲觀鎖短,樂觀鎖能夠用較大的鎖粒度得到較好的併發訪問性能。可是若是第二個用戶剛好在第一個用戶提交更改以前讀取了該對象,那麼當他完成了本身的更改進行提交時,數據庫就會發現該對象已經變化了,這樣,第二個用戶不得不從新讀取該對象並做出更改。這說明在樂觀鎖環境中,會增長併發用戶讀取對象的次數。
從數據庫廠商的角度看,使用樂觀的頁鎖是比較好的,尤爲在影響不少行的批量操做中能夠放比較少的鎖,從而下降對資源的需求提升數據庫的性能。再考慮彙集索引。在數據庫中記錄是按照彙集索引的物理順序存放的。若是使用頁鎖,當兩個用戶同時訪問更改位於同一數據頁上的相鄰兩行時,其中一個用戶必須等待另外一個用戶釋放鎖,這會明顯地下降系統的性能。interbase和大多數關係數據庫同樣,採用的是樂觀鎖,並且讀鎖是共享的,寫鎖是排他的。能夠在一個讀鎖上再放置讀鎖,但不能再放置寫鎖;你不能在寫鎖上再放置任何鎖。鎖是目前解決多用戶併發訪問的有效手段。
綜上所述:在實際生產環境裏邊,若是併發量不大且不容許髒讀,可使用悲觀鎖解決併發問題;但若是系統的併發很是大的話,悲觀鎖定會帶來很是大的性能問題,因此咱們就要選擇樂觀鎖定的方法.
悲觀鎖應用
須要使用數據庫的鎖機制,好比SQL SERVER 的TABLOCKX(排它表鎖) 此選項被選中時,SQL Server 將在整個表上置排它鎖直至該命令或事務結束。這將防止其餘進程讀取或修改表中的數據。
SqlServer中使用
Begin Tran
select top 1 @TrainNo=T_NO
from Train_ticket with (UPDLOCK) where S_Flag=0
update Train_ticket
set T_Name=user,
T_Time=getdate(),
S_Flag=1
where T_NO=@TrainNo
commit
咱們在查詢的時候使用了with (UPDLOCK)選項,在查詢記錄的時候咱們就對記錄加上了更新鎖,表示咱們即將對此記錄進行更新. 注意更新鎖和共享鎖是不衝突的,也就是其餘用戶還能夠查詢此表的內容,可是和更新鎖和排它鎖是衝突的.因此其餘的更新用戶就會阻塞.
在此:舉個簡單的例子來講明悲觀鎖的應用,咱們以SQLServer爲例進行說明:
假如兩個線程同時修改數據庫同一條記錄,就會致使後一條記錄覆蓋前一條,從而引起一些問題。
例如:
一個售票系統有一個餘票數,客戶端每調用一次出票方法,餘票數就減一。
情景:
總共300張票,假設兩個售票點,剛好在同一時間出票,它們作的操做都是先查詢餘票數,而後減一。
通常的sql語句:
問題就在於,同一時間獲取的餘票都爲300,每一個售票點都作了一次更新爲299的操做,致使餘票少了1,而實際出了兩張票。
打開兩個查詢窗口,分別快速運行以上代碼便可看到效果。
所以:在此咱們能夠採用悲觀鎖進行解決
在查詢的時候加了一個更新鎖,保證自查詢起直到事務結束不會被其餘事務讀取修改,避免產生髒數據。
從而能夠解決上述問題。
若是這個售票系統併發過高(例如:春節售票,十月一國慶售票等買票人員併發操做很高),咱們採用上述的悲觀鎖方案就會是系統性能大大下降。譬如:售票員A鎖定了這個操做,售票員A在處理這個業務的過程當中,又去喝了杯咖啡,在此期間,其餘業務員是沒法進行訂票的,因此...
樂觀鎖解決方案:
這即是樂觀鎖的解決方案,能夠解決併發帶來的數據錯誤問題,但不保證每一次調用更新都成功,可能會返回'更新失敗'
悲觀鎖和樂觀鎖
悲觀鎖必定成功,但在併發量特別大的時候會形成很長堵塞甚至超時,僅適合小併發的狀況。
樂觀鎖不必定每次都修改爲功,但能充分利用系統的併發處理機制,在大併發量的時候效率要高不少。
下面以SQLSERVER爲例,詳情說明悲觀鎖和樂觀鎖(請務必看懂關於樂觀鎖的實現,由於:在實際的系統中,樂觀鎖應用較爲普遍)
鎖( locking )
業務邏輯的實現過程當中,每每須要保證數據訪問的排他性。如在金融系統的日終結算處理中,咱們但願針對某個 cut-off 時間點的數據進行處理,而不但願在結算進行過程當中(多是幾秒種,也多是幾個小時),數據再發生變化。此時,咱們就須要經過一些機制來保證這些數據在某個操做過程當中不會被外界修改,這樣的機制,在這裏,也就是所謂的 「 鎖 」 ,即給咱們選定的目標數據上鎖,使其沒法被其餘程序修改。
Hibernate 支持兩種鎖機制:即一般所說的 「 悲觀鎖( Pessimistic Locking ) 」和 「 樂觀鎖( Optimistic Locking ) 」 。
悲觀鎖( Pessimistic Locking )
悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)。
一個典型的倚賴數據庫的悲觀鎖調用:
select * from account where name=」Erica」 for update
這條 sql 語句鎖定了 account 表中全部符合檢索條件( name=」Erica」 )的記錄。
本次事務提交以前(事務提交時會釋放事務過程當中的鎖),外界沒法修改這些記錄。
Hibernate 的悲觀鎖,也是基於數據庫的鎖機制實現。
Hibernate 的加鎖模式有:
Ø LockMode.NONE : 無鎖機制。
Ø LockMode.WRITE : Hibernate 在 Insert 和 Update 記錄的時候會自動獲取。
Ø LockMode.READ : Hibernate 在讀取記錄的時候會自動獲取。
樂觀鎖( Optimistic Locking )
相對悲觀鎖而言,樂觀鎖機制採起了更加寬鬆的加鎖機制。悲觀鎖大多數狀況下依 靠數據庫的鎖機制實現,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。
如一個金融系統,當某個操做員讀取用戶的數據,並在讀出的用戶數據的基礎上進 行修改時(如更改用戶賬戶餘額),若是採用悲觀鎖機制,也就意味着整個操做過 程中(從操做員讀出數據、開始修改直至提交修改結果的全過程,甚至還包括操做員中途去煮咖啡的時間),數據庫記錄始終處於加鎖狀態,能夠想見,若是面對幾百上千個併發,這樣的狀況將致使怎樣的後果。
樂觀鎖機制在必定程度上解決了這個問題。樂觀鎖,大可能是基於數據版本 Version )記錄機制實現。何謂數據版本?即爲數據增長一個版本標識,在基於(數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個 「version」 字段來實現。讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。
對於上面修改用戶賬戶信息的例子而言,假設數據庫中賬戶信息表中有一個version 字段,當前值爲 1 ;而當前賬戶餘額字段( balance )爲 $100 。
1 操做員 A 此時將其讀出( version=1 ),並從其賬戶餘額中扣除 $50( $100-$50 )。
2 在操做員 A 操做的過程當中,操做員 B 也讀入此用戶信息( version=1 ),並從其賬戶餘額中扣除 $20 ( $100-$20 )。
3 操做員 A 完成了修改工做,將數據版本號加一( version=2 ),連同賬戶扣除後餘額( balance=$50 ),提交至數據庫更新,此時因爲提交數據版本大於數據庫記錄當前版本,數據被更新,數據庫記錄 version 更新爲 2 。
4 操做員 B 完成了操做,也將版本號加一( version=2 )試圖向數據庫提交數據( balance=$80 ),但此時比對數據庫記錄版本時發現,操做員 B 提交的數據版本號爲 2 ,數據庫記錄當前版本也爲 2 ,不知足 「 提交版本必須大於記錄當前版本才能執行更新 「 的樂觀鎖策略,所以,操做員 B 的提交被駁回。
這樣,就避免了操做員 B 用基於 version=1 的舊數據修改的結果覆蓋操做員 A 的操做結果的可能。
從上面的例子能夠看出,樂觀鎖機制避免了長事務中的數據庫加鎖開銷(操做員 A和操做員 B 操做過程當中,都沒有對數據庫數據加鎖),大大提高了大併發量下的系統總體性能表現.
須要注意的是,樂觀鎖機制每每基於系統中的數據存儲邏輯,所以也具有必定的侷限性,如在上例中,因爲樂觀鎖機制是在咱們的系統中實現,來自外部系統的用戶餘額更新操做不受咱們系統的控制,所以可能會形成髒數據被更新到數據庫中。在系統設計階段,咱們應該充分考慮到這些狀況出現的可能性,並進行相應調整(如將樂觀鎖策略在數據庫存儲過程當中實現,對外只開放基於此存儲過程的數據更新途徑,而不是將數據庫表直接對外公開)。
固然:除了SQL上的鎖能夠解決數據併發外,咱們也能夠結合編程語言來實現併發的控制。
在此:以C#爲例,咱們能夠經過代碼臨界區的定義來實現併發的控制。在C#語言中,定義一個代碼臨界區使用的關鍵字是LOCK,在此,小弟不做詳細介紹,僅僅做爲提示你們,有興趣的小夥伴能夠自行查閱關於C#臨界區代碼關鍵字LOCK的使用,本人系列博客:模擬併發/C# 併發處理 鎖OR線程