一、什麼是悲觀鎖,樂觀鎖mysql
悲觀鎖大多數狀況下依靠數據庫的鎖機制實現,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。而樂觀鎖機制在必定程度上解決了這個問題。樂觀鎖,大可能是基於數據版本( Version )記錄機制實現。何謂數據版本?即爲數據增長一個版本標識,在基於數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個 「version」 字段來實現。讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。sql
悲觀鎖:假定會發生併發衝突,屏蔽一切可能違反數據完整性的操做。[1]數據庫
樂觀鎖:假設不會發生併發衝突,只在提交操做時檢查是否違反數據完整性。[1]樂觀鎖不能解決髒讀的問題。api
1. 使用自增加的整數表示數據版本號。更新時檢查版本號是否一致,好比數據庫中數據版本爲6,更新提交時version=6+1,使用該version值(=7)與數據庫version+1(=7)做比較,若是相等,則能夠更新,若是不等則有可能其餘程序已更新該記錄,因此返回錯誤。mybatis
2. 使用時間戳來實現.併發
注:對於以上兩種方式,Hibernate自帶實現方式:在使用樂觀鎖的字段前加annotation: @Version, Hibernate在更新時自動校驗該字段。性能
須要使用數據庫的鎖機制,好比SQL SERVER 的TABLOCKX(排它表鎖) 此選項被選中時,SQL Server 將在整個表上置排它鎖直至該命令或事務結束。這將防止其餘進程讀取或修改表中的數據。spa
數據庫鎖機制:
共享鎖:由讀表操做加上的鎖,加鎖後其餘用戶只能獲取該表或行的共享鎖,不能獲取排它鎖,也就是說只能讀不能寫。.net
排它鎖:由寫表操做加上的鎖,加鎖後其餘用戶不能獲取該表或行的任何鎖。hibernate
鎖的範圍:
行鎖: 對某行記錄加上鎖
表鎖: 對整個表加上鎖
這樣組合起來就有,行級共享鎖,表級共享鎖,行級排他鎖,表級排他鎖。
在實際生產環境裏邊,若是併發量不大且不容許髒讀,可使用悲觀鎖解決併發問題;但若是系統的併發很是大的話,悲觀鎖定會帶來很是大的性能問題,因此咱們就要選擇樂觀鎖定的方法.
二、事務併發會產生什麼問題
1)第一類丟失更新:在沒有事務隔離的狀況下,兩個事務都同時更新一行數據,可是第二個事務卻中途失敗退出, 致使對數據的兩個修改都失效了。
例如:
張三的工資爲5000,事務A中獲取工資爲5000,事務B獲取工資爲5000,匯入100,並提交數據庫,工資變爲5100,
隨後事務A發生異常,回滾了,恢復張三的工資爲5000,這樣就致使事務B的更新丟失了。
2)髒讀:髒讀就是指當一個事務正在訪問數據,而且對數據進行了修改,而這種修改尚未提交到數據庫中,這時,另一個事務也訪問這個數據,而後使用了這個數據。
例如:
張三的工資爲5000,事務A中把他的工資改成8000,但事務A還沒有提交。
與此同時,
事務B正在讀取張三的工資,讀取到張三的工資爲8000。
隨後,
事務A發生異常,而回滾了事務。張三的工資又回滾爲5000。
最後,
事務B讀取到的張三工資爲8000的數據即爲髒數據,事務B作了一次髒讀。
3)不可重複讀:是指在一個事務內,屢次讀同一數據。在這個事務尚未結束時,另一個事務也訪問該同一數據。那麼,在第一個事務中的兩次讀數據之間,因爲第二個事務的修改,那麼第一個事務兩次讀到的的數據多是不同的。這樣就發生了在一個事務內兩次讀到的數據是不同的,所以稱爲是不可重複讀。
例如:
在事務A中,讀取到張三的工資爲5000,操做沒有完成,事務還沒提交。
與此同時,
事務B把張三的工資改成8000,並提交了事務。
隨後,
在事務A中,再次讀取張三的工資,此時工資變爲8000。在一個事務中先後兩次讀取的結果並不致,致使了不可重複讀。
4)第二類丟失更新:不可重複讀的特例。有兩個併發事務同時讀取同一行數據,而後其中一個對它進行修改提交,而另外一個也進行了修改提交。這就會形成第一次寫操做失效。
例如:
在事務A中,讀取到張三的存款爲5000,操做沒有完成,事務還沒提交。
與此同時,
事務B,存儲1000,把張三的存款改成6000,並提交了事務。
隨後,
在事務A中,存儲500,把張三的存款改成5500,並提交了事務,這樣事務A的更新覆蓋了事務B的更新。
5)幻讀:是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的所有數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那麼,之後就會發生操做第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺同樣。
例如:
目前工資爲5000的員工有10人,事務A讀取全部工資爲5000的人數爲10人。
此時,
事務B插入一條工資也爲5000的記錄。
這是,事務A再次讀取工資爲5000的員工,記錄爲11人。此時產生了幻讀。
提醒:
不可重複讀的重點是修改:
一樣的條件,你讀取過的數據,再次讀取出來發現值不同了
幻讀的重點在於新增或者刪除:
一樣的條件,第 1 次和第 2 次讀出來的記錄數不同
三、事務隔離級別,解決什麼併發問題
(1)READ_UNCOMMITTED
這是事務最低的隔離級別,它充許別外一個事務能夠看到這個事務未提交的數據。
會出現髒讀、不可重複讀、幻讀 (隔離級別最低,併發性能高)。
(2)READ_COMMITTED
保證一個事務修改的數據提交後才能被另一個事務讀取。另一個事務不能讀取該事務未提交的數據。
能夠避免髒讀,但會出現不可重複讀、幻讀問題(鎖定正在讀取的行)。
(3)REPEATABLE_READ
能夠防止髒讀、不可重複讀,但會出幻讀(鎖定所讀取的全部行)。
(4)SERIALIZABLE
這是花費最高代價可是最可靠的事務隔離級別,事務被處理爲順序執行。
保證全部的狀況不會發生(鎖表)。
詳情見下表:
提醒:
Mysql默認的事務隔離級別爲repeatable_read
四、悲觀鎖和樂觀鎖與數據庫隔離級別的關係
關係總結,事務隔離級別是併發控制的總體解決方案,其其實是綜合利用各類類型的鎖和行版本控制,來解決併發問題。鎖是數據庫併發控制的內部機制,是基礎。對用戶來講,只有當事務隔離級別沒法解決一些併發問題和需求時,纔有必要在語句中手動設置鎖。
五、悲觀鎖和樂觀鎖的使用
1)悲觀鎖:
要使用悲觀鎖,咱們必須關閉mysql數據庫的自動提交屬性,由於MySQL默認使用autocommit模式,也就是說,當你執行一個更新操做後,MySQL會馬上將結果進行提交。
使用mybatis或者jdbc api實現時:
sql語句:select status from t_goods where id=1 for update;,update t_goods set status=2;,這時id爲1的記錄會被鎖住
使用hibernate實現時,
Criteria.setLockMode
Query.setLockMode
Session.lock
注意,只有在查詢開始以前(也就是Hiberate 生成SQL 以前)設定加鎖,纔會 真正經過數據庫的鎖機制進行加鎖處理,不然,數據已經經過不包含for update 子句的Select SQL加載進來,所謂數據庫加鎖也就無從談起。
2)樂觀鎖:
樂觀鎖是系統層面的實現,推薦的實現方案是表中新增一個version字段,而後基於version字段的更新來判斷事務是否有效。
使用mybatis或者jdbc api實現時:
select (status,status,version) from t_goods where id=#{id}
update t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};
更新操做是基於version的,當事務A更新了記錄以後,version字段會加1,事務B再根據以前的version去查找記錄的時候,數據庫沒有相應的記錄就會出現異常,這時系統捕捉異常進行處理,便可避免併發的問題。
使用hibernate實現時:
Hibernate中必須指定optimistic-lock屬性對version描述符指定。