随笔感悟:Mysql悲观锁和乐观锁
目錄
首先是場景:并發控制
悲觀鎖
?select ...?for update的使用
?樂觀鎖
解釋:
特點:
實現:
sql演示例子:
?結合我們的Java代碼的實現
以上更新語句存在一個比較嚴重的問題,即ABA問題:
解決方式:
優化
?
首先是場景:并發控制
?為什么要使用悲觀鎖和樂觀鎖——>為了并發情況下,線程跟自己在單機情況一樣得到相同的結果,保證數據的一致性;
沒做好并發控制就會可能出現:臟讀、不可重復讀、幻讀等問題;
結論:樂觀鎖適合并發量比較小的場景(讀多寫少),悲觀鎖適合并發量比較大的場景(寫多多少);
?
悲觀鎖
目的:在并發時保證線程和單機時結果一樣(宏觀);當在數據庫中修改某條數據時,防止同時被其他人進行修改從而造成數據不一致(微觀);——>(處理:加鎖防止并發,利用鎖,線程鎖住資源防止被其他人修改);
場景:有點像警察處理兇殺現場,對兇殺現場(資源對象)進行上鎖,防止其他人對其現場進行修改;
特點:具有強烈的獨占和排他特性。它指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度
實現:像我們的表鎖、行鎖都是悲觀鎖,在java中就是synchronized關鍵字實現
?然后我們悲觀鎖主要分為共享鎖和排他鎖
- 共享鎖【shared locks】又稱為讀鎖,簡稱 S 鎖。顧名思義,共享鎖就是多個事務對于同一數據可以共享一把鎖,都能訪問到數據,但是只能讀不能修改。
- 排他鎖【exclusive locks】又稱為寫鎖,簡稱 X 鎖。顧名思義,排他鎖就是不能與其他鎖并存,如果一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的其他鎖,包括共享鎖和排他鎖。獲取排他鎖的事務可以對數據行讀取和修改。(比如行鎖和表鎖)
例子:
1.首先事務1先開啟事務——>2.然后事務2開啟事務,事務1修改id=10001的數據——>3.然后事務2修改同樣為id=10001的數據發現阻塞——>結論:驗證了id=10001被行鎖鎖住了(如果沒有索引的話就是表鎖)
?
5. 最后事務1回滾,釋放鎖資源,事務2又可以執行了
?select ...?for update的使用
? 1.事務1先開啟事務查詢id=10001的數據——>2.事務2開啟查詢事務1一樣的數據發現阻塞——>3.然后事務1回滾或者commit即可
?樂觀鎖
解釋:
樂觀鎖是相對悲觀鎖而言的,樂觀鎖假設數據一般情況不會造成沖突,所以在數據進行提交更新的時候(重點),才會正式對數據的沖突與否進行檢測,如果沖突,則返回給用戶異常信息,讓用戶決定如何去做。樂觀鎖適用于讀多寫少的場景,這樣可以提高程序的吞吐量
特點:
1.更加寬松,性能較好,吞吐量較高,有效避免了死鎖等現象
2.但是可能會出現cpu空轉的現象,造成cpu飆高
可能會出現線程一直retry,并且ABA問題也會出現,中途被其他線程改了比較的version,然后又改回來,中途做了其他事情
實現:
1.CAS實現
2.版本號控制,一般像我們mysql中在表中加一個字段version版本號:表示數據被修改的次數;當數據被修改時,verson+1;在線程更新時,在讀取數據的時候也要讀取version,在提交更新時,若剛才讀取到的version值和當前數據庫中的version值相等才更新,否則retry;
sql演示例子:
當多個線程嘗試使用 CAS 同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試。比如前面的扣減庫存問題,通過樂觀鎖可以實現如下
//1.查詢數據的sql select * from tb_item_stock where item_id=10001; //2.修改數據的sql update tb_item_stock set stock=stock+1,version=version+1 where item_id=10001 and version=1;事務1:先select一下看下版本,下面圖片是事務1第二次select(也就是在事務2提交更新sql后的一個操作)
?事務2:
?結合我們的Java代碼的實現
/*** 模擬多版本號*/@Transactionalpublic void deduct2(){//1.首先查詢倉庫里面的內容List<Stock> stocks = this.stockMapper.queryStock("10001");//獲取第一條倉庫記錄Stock stock = stocks.get(0);//2.判斷倉庫是否充足if(stock!=null&&stock.getStock()>0){//2.1扣減庫存,然后設置新的版本號進行更新stock.setStock(stock.getStock()-1);Integer version = stock.getVersion(); //獲取當前商品版本stock.setVersion(version+1); //更新一次版本加一次,如果中途有其他的更新操作就會失敗//2.2然后進行更新——>需要進行判斷如果更新影響行數為0就進行cas操作if(this.stockMapper.update(stock,new UpdateWrapper<Stock>().eq("item_id",stock.getItemId()).eq("version",version))==0){this.deduct2();//cas}}以上更新語句存在一個比較嚴重的問題,即ABA問題:
解決方式:
我們可以加一個時間戳,因為時間戳天然具有順序遞增性;
還一個場景,就是一旦遇上高并發的時候,就只有一個線程可以修改成功,那么就會存在大量的失敗。對于像淘寶這樣的電商網站,高并發是常有的事,總讓用戶感知到失敗顯然是不合理的。所以,還是要想辦法減少樂觀鎖的粒度。一個比較好的建議,就是減小樂觀鎖力度,最大程度的提升吞吐率,提高并發能力!
?
?比如我們的AutomicInteger就是利用cas
public final boolean compareAndSet(int expectedValue, int newValue) {return U.compareAndSetInt(this, VALUE, expectedValue, newValue);}優化
LongAdder,它就是嘗試使用分段 CAS 以及自動分段遷移的方式來大幅度提升多線程高并發執行 CAS 操作的性能,這個類具體是如何優化性能的呢?如圖:
?LongAdder 核心思想就是熱點分離,這一點和?ConcurrentHashMap?的設計思想相似。就是將 value 值分離成一個數組,當多線程訪問時,通過 hash 算法映射到其中的一個數字進行計數。而最終的結果,就是這些數組的求和累加。這樣一來,就減小了鎖的粒度
?(41條消息) LongAdd_Fairy要carry的博客-CSDN博客_longadd
總結
以上是生活随笔為你收集整理的随笔感悟:Mysql悲观锁和乐观锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MPTCP iperf 发包方式
- 下一篇: css3地球自转,CSS3 月亮围绕地球