數據的鎖定分爲兩種,第一種叫做悲觀鎖,第二種叫做樂觀鎖。html
一、悲觀鎖,就是對數據的衝突採起一種悲觀的態度,也就是說假設數據確定會衝突,因此在數據開始讀取的時候就把數據鎖定住。【數據鎖定:數據將暫時不會獲得修改】java
二、樂觀鎖,認爲數據通常狀況下不會形成衝突,因此在數據進行提交更新的時候,纔會正式對數據的衝突與否進行檢測,若是發現衝突了,則讓用戶返回錯誤的信息。讓用戶決定如何去作。sql
理解:數據庫
1. 樂觀鎖是一種思想,具體實現是,表中有一個版本字段,第一次讀的時候,獲取到這個字段。處理完業務邏輯開始更新的時候,須要再次查看該字段的值是否和第一次的同樣。若是同樣更新,反之拒絕。服務器
之因此叫樂觀,由於這個模式沒有從數據庫加鎖。session
2. 悲觀鎖是讀取的時候爲後面的更新加鎖,以後再來的讀操做都會等待。這種是數據庫鎖併發
樂觀鎖優勢程序實現,不會存在死鎖等問題。他的適用場景也相對樂觀。阻止不了除了程序以外的數據庫操做。app
悲觀鎖是數據庫實現,他阻止一切數據庫操做。框架
再來講更新數據丟失,全部的讀鎖都是爲了保持數據一致性。樂觀鎖若是有人在你以前更新了,你的更新應當是被拒絕的,可讓用戶重新操做。悲觀鎖則會等待前一個更新完成。這也是區別。具體業務具體分析
實現:ide
1、悲觀鎖
一、排它鎖,當事務在操做數據時把這部分數據進行鎖定,直到操做完畢後再解鎖,其餘事務操做纔可操做該部分數據。這將防止其餘進程讀取或修改表中的數據。
二、實現:大多數狀況下依靠數據庫的鎖機制實現
通常使用 select ...for update 對所選擇的數據進行加鎖處理,例如select * from account where name=」Max」 for update, 這條sql 語句鎖定了account 表中全部符合檢索條件(name=」Max」)的記錄。本次事務提交以前(事務提交時會釋放事務過程當中的鎖),外界沒法修改這些記錄。
2、樂觀鎖
一、若是有人在你以前更新了,你的更新應當是被拒絕的,可讓用戶從新操做。
二、實現:大多數基於數據版本(Version)記錄機制實現
具體可經過給表加一個版本號或時間戳字段實現,當讀取數據時,將version字段的值一同讀出,數據每更新一次,對此version值加一。當咱們提交更新的時候,判斷當前版本信息與第一次取出來的版本值大小,若是數據庫表當前版本號與第一次取出來的version值相等,則予以更新,不然認爲是過時數據,拒絕更新,讓用戶從新操做。
3、ORM框架中悲觀鎖樂觀鎖的應用
通常悲觀鎖、樂觀鎖都須要都經過sql語句的設定、數據的設計結合代碼來實現,例如樂觀鎖中的版本號字段,單純面向數據庫操做,是須要本身來實現樂觀鎖的,簡言之,也就是版本號或時間戳字段的維護是程序本身維護的,自增、判斷大小肯定是否更新都經過代碼判斷實現。數據庫進提供了樂觀、悲觀兩個思路進行併發控制。
對於經常使用java 持久化框架,對於數據庫的這一機制都有本身的實現,以Hibernate爲例,總結一下ORM框架中悲觀鎖樂觀鎖的應用
一、Hibernate的悲觀鎖:
基於數據庫的鎖機制實現。以下查詢語句:
- String hqlStr ="from TUser as user where user.name=Max";
- Query query = session.createQuery(hqlStr);
- query.setLockMode("user",LockMode.UPGRADE); //加鎖
- List userList = query.list();//執行查詢,獲取數據
觀察運行期Hibernate生成的SQL語句:
- select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex from t_user tuser0_ where (tuser0_.name='Erica' ) for update
這裏Hibernate經過使用數據庫的for update子句實現了悲觀鎖機制。對返回的全部user記錄進行加鎖。
二、Hibernate的加鎖模式有:
Ø LockMode.NONE : 無鎖機制。
Ø LockMode.WRITE :Hibernate在寫操做(Insert和Update)時會自動
獲取寫鎖。
Ø LockMode.READ : Hibernate在讀取記錄的時候會自動獲取。
這三種鎖機制通常由Hibernate內部使用,如Hibernate爲了保證Update
過程當中對象不會被外界修改,會在save方法實現中自動爲目標對象加上WRITE鎖。
Ø LockMode.UPGRADE :利用數據庫的for update子句加鎖。
Ø LockMode. UPGRADE_NOWAIT :Oracle的特定實現,利用Oracle的for update nowait子句實現加鎖。
注意,只有在查詢開始以前(也就是Hiberate 生成SQL 以前)設定加鎖,纔會
真正經過數據庫的鎖機制進行加鎖處理,不然,數據已經經過不包含for update
子句的Select SQL加載進來,所謂數據庫加鎖也就無從談起。
三、Hibernate的樂觀鎖
Hibernate 在其數據訪問引擎中內置了樂觀鎖實現。若是不用考慮外部系統對數據庫的更新操做,利用Hibernate提供的透明化樂觀鎖實現,將大大提高咱們的生產力。Hibernate中能夠經過class描述符的optimistic-lock屬性結合version描述符指定。具體實現方式以下:
如今,咱們爲以前示例中的TUser加上樂觀鎖機制。
實現1、 配置optimistic-lock屬性:
- <hibernate-mapping>
- <class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true" optimistic-lock="version">
- ……
- </class>
- </hibernate-mapping>
optimistic-lock屬性有以下可選取值:
Ø none:
無樂觀鎖
Ø version:
經過版本機制實現樂觀鎖
Ø dirty:
經過檢查發生變更過的屬性實現樂觀鎖
Ø all:
經過檢查全部屬性實現樂觀鎖
經過version實現的樂觀鎖機制是Hibernate官方推薦的樂觀鎖實現,同時也是Hibernate中,目前惟一在數據對象脫離Session發生修改的狀況下依然有效的鎖機制。所以,通常狀況下,咱們都選擇version方式做爲Hibernate樂觀鎖實現機制。
實現2、添加一個Version屬性描述符
- <hibernate-mapping>
- <class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true" optimistic-lock="version">
- <id name="id" column="id" type="java.lang.Integer">
- <generator class="native"/>
- </id>
- <version column="version" name="version" type="java.lang.Integer"/>
- ……
- </class>
- </hibernate-mapping>
注意version 節點必須出如今ID 節點以後。
這裏聲明瞭一個version屬性,用於存放用戶的版本信息,保存在TUser表的
version字段中。
測試:
此時若是咱們嘗試編寫一段代碼,更新TUser表中記錄數據,如:
- Criteria criteria = session.createCriteria(TUser.class);
- criteria.add(Expression.eq("name","Max"));
- List userList = criteria.list();
- TUser user =(TUser)userList.get(0);
- Transaction tx = session.beginTransaction();
- user.setUserType(1); //更新UserType字段
- tx.commit();
每次對TUser進行更新的時候,咱們能夠發現,數據庫中的version都在遞增。
而若是咱們嘗試在tx.commit 以前,啓動另一個Session,對名爲Max的用
戶進行操做,下面模擬併發更新時的狀況:
- Session session= getSession();
- Criteria criteria = session.createCriteria(TUser.class);
- criteria.add(Expression.eq("name","Max"));
- Session session2 = getSession();
- Criteria criteria2 = session2.createCriteria(TUser.class);
- criteria2.add(Expression.eq("name","Max"));
- List userList = criteria.list();
- List userList2 = criteria2.list();TUser user =(TUser)userList.get(0);
- TUser user2 =(TUser)userList2.get(0);
- Transaction tx = session.beginTransaction();
- Transaction tx2 = session2.beginTransaction();
- user2.setUserType(99);
- tx2.commit();
- user.setUserType(1);
- tx.commit();
執行併發更新的代碼,在tx.commit()處拋出StaleObjectStateException異
常,並指出版本檢查失敗,當前事務正在試圖提交一個過時數據。經過捕捉這個異常,我
們就能夠在樂觀鎖校驗失敗時進行相應處理。
這就是hibernate實現悲觀鎖和樂觀鎖的主要方式。
4、總結
悲觀鎖相對比較謹慎,設想現實狀況應該很容易就發生衝突,因此我仍是獨佔數據資源吧。
樂觀鎖就想得開並且很是聰明,應該是不會有什麼衝突的,我對錶使用一個時間戳或者版本號,每次讀、更新操做都對這個字段進行比對,若是在我以前已經有人對數據進行更新了,那就讓它更新,大不了我再讀一次或者再更新一次。
樂觀鎖的管理跟SVN管理代碼版本的原理很像,若是在我提交代碼以前用本地代碼的版本號與服務器作對比,若是本地版本號小於服務器上最新版本號,則提交失敗,產生衝突代碼,讓用戶決定選擇哪一個版本繼續使用。
在實際生產環境裏邊,若是併發量不大且不容許髒讀,可使用悲觀鎖;但若是系統的併發很是大的話,悲觀鎖定會帶來很是大的性能問題,因此咱們就要選擇樂觀鎖定的方法 另外,Mysql在處理併發訪問數據上,還有添加
讀鎖(共享鎖)、寫鎖(排它鎖),控制
鎖粒度【表鎖(table lock)、行級鎖(row lock)】等實現,有興趣能夠繼續研究。