我是????廖志偉????,一名????Java開發工程師????、????Java領域優質創作者????、????CSDN部落格專家????、????幕後大佬社群創始人????。擁有多年一線研發經驗,研究過各種常見架構及中間件的底層源碼,對于大型分布式、微服務、三高架構(高性能、高并發、高可用)有過實踐架構經驗。
文章目錄
- 悲觀鎖/樂觀鎖/排它鎖/共享鎖/表級鎖/行級鎖/死鎖
- 悲觀鎖
- 樂觀鎖
- 行鎖和表鎖
- 表級鎖
- 行鎖
- 死鎖
本文内容:
悲觀鎖/樂觀鎖/排它鎖/共享鎖/表級鎖/行級鎖/死鎖

悲觀鎖
每次去拿資料的時候都認為别人會修改,是以每次在拿資料的時候都會上鎖,這樣别人想拿這個資料就會block直到它拿到鎖。适用于寫為居多的場景下。比如行鎖,表鎖等,讀鎖,寫鎖,syncronized實作的鎖等。sql中實作悲觀鎖,使用for update對資料加鎖,例如:select num from goods where id = 1 for update;
樂觀鎖
每次去拿資料的時候都認為别人不會修改,是以不會上鎖,但是在更新的時候會判斷一下在此期間别人有沒有去更新這個資料,在表中增加一個版本(version)或時間戳(timestamp)來實作。适用于讀為居多的場景下。樂觀鎖适用于多讀的應用類型,這樣可以提高吞吐量。
工作流程
擷取目前資料版本
更新操作版本号+1
送出更新時,擷取版本号
比較送出時的版本号與第一次擷取的版本号,如果一緻,那麼認為資源是最新的,可以更新
否則復原或者抛出異常
案例:
事務一開啟,男櫃員先執行讀操作,取出金額和版本号,執行寫操作,此時金額改為 120,版本号為1,事務還沒有送出
事務二開啟,女櫃員先執行讀操作,取出金額和版本号,執行寫操作,此時金額改為 50,版本号變為 1,事務未送出
現在送出事務一,金額改為 120,版本變為1,送出事務。理想情況下應該變為 金額 = 50,版本号 = 2,但是實際上事務二 的更新是建立在金額為 100 和 版本号為 0 的基礎上的,是以事務二不會送出成功,應該重新讀取金額和版本号,再次進行寫操作。這樣,就避免了女櫃員 用基于 version=0 的舊資料修改的結果覆寫男操作員操作結果的可能。
樂觀鎖和悲觀鎖的使用:(高性能、高可用、高并發)來說,悲觀鎖的實作用的越來越少了,但是一般多讀的情況下還是需要使用悲觀鎖的,因為雖然加鎖的性能比較低,但是也阻止了像樂觀鎖一樣,遇到寫不一緻的情況下一直重試的時間。使用悲觀鎖鎖住某個資料後,再遇到其他需要修改資料的操作,那麼此操作就無法完成金額的修改,對産品來說是災難性的一刻,使用樂觀鎖的版本号機制能夠解決這個問題。
行鎖和表鎖
MySQL常用引擎有MyISAM和InnoDB,而InnoDB是mysql預設的引擎。MyISAM不支援行鎖,而InnoDB支援行鎖和表鎖。
如何加鎖?MyISAM在執行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,在執行更新操作(UPDATE、DELETE、INSERT等)前,會自動給涉及的表加寫鎖,這個過程并不需要使用者幹預,是以使用者一般不需要直接用LOCK TABLE指令給MyISAM表顯式加鎖。
顯式加鎖:
上共享鎖(讀鎖)的寫法:lock in share mode,例如:select math from zje where math>60 lock in share mode;
上排它鎖(寫鎖)的寫法:for update,例如:select math from zje where math >60 for update;
表級鎖
表鎖:不會出現死鎖,發生鎖沖突幾率高,并發低。
MySQL的表級鎖有兩種模式:
表共享讀鎖
表獨占寫鎖
讀鎖會阻塞寫,寫鎖會阻塞讀和寫
對MyISAM表的讀操作,不會阻塞其它程序對同一表的讀請求,但會阻塞對同一表的寫請求。隻有當讀鎖釋放後,才會執行其它程序的寫操作。
對MyISAM表的寫操作,會阻塞其它程序對同一表的讀和寫操作,隻有當寫鎖釋放後,才會執行其它程序的讀寫操作。
MyISAM不适合做寫為主表的引擎,因為寫鎖後,其它線程不能做任何操作,大量的更新會使查詢很難得到鎖,進而造成永遠阻塞
行鎖:會出現死鎖,發生鎖沖突幾率低,并發高。
行鎖
在MySQL的InnoDB引擎支援行鎖,與Oracle不同,MySQL的行鎖是通過索引加載的,也就是說,行鎖是加在索引響應的行上的,要是對應的SQL語句沒有走索引,則會全表掃描,行鎖則無法實作,取而代之的是表鎖,此時其它事務無法對目前表進行更新或插入操作。
for update:如果在一條select語句後加上for update,則查詢到的資料會被加上一條排它鎖,其它事務可以讀取,但不能進行更新和插入操作。
行鎖的實作需要注意:
行鎖必須有索引才能實作,否則會自動鎖全表,那麼就不是行鎖了。
兩個事務不能鎖同一個索引。
insert,delete,update在事務中都會自動預設加上排它鎖。
行鎖場景:
A使用者消費,service層先查詢該使用者的賬戶餘額,若餘額足夠,則進行後續的扣款操作;這種情況查詢的時候應該對該記錄進行加鎖。否則,B使用者在A使用者查詢後消費前先一步将A使用者賬号上的錢轉走,而此時A使用者已經進行了使用者餘額是否足夠的判斷,則可能會出現餘額已經不足但卻扣款成功的情況。為了避免此情況,需要在A使用者操作該記錄的時候進行for update加鎖。
死鎖
産生死鎖的四個必要條件
互斥條件:程序要求對所配置設定的資源(如列印機)進行排他性控制,即在一段時間内某資源僅為一個程序所占有。此時若有其他程序請求該資源,則請求程序隻能等待。
不可剝奪條件:程序所獲得的資源在未使用完畢之前,不能被其他程序強行奪走,即隻能由獲得該資源的程序自己來釋放(隻能是主動釋放)。
請求與保持條件:程序已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其他程序占有,此時請求程序被阻塞,但對自己已獲得的資源保持不放。
循環等待條件:存在一種程序資源的循環等待鍊,鍊中每一個程序已獲得的資源同時被 鍊中下一個程序所請求。即存在一個處于等待狀态的程序集合{Pl, P2, …, pn},其中Pi等 待的資源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的資源被P0占有
處理死鎖的方法:
預防死鎖:通過設定某些限制條件,去破壞産生死鎖的四個必要條件中的一個或幾個條件,來防止死鎖的發生。
避免死鎖:在資源的動态配置設定過程中,用某種方法去防止系統進入不安全狀态,進而避免死鎖的發生。
檢測死鎖:允許系統在運作過程中發生死鎖,但可設定檢測機構及時檢測死鎖的發生,并采取适當措施加以清除。
解除死鎖:當檢測出死鎖後,便采取适當措施将程序從死鎖狀态中解脫出來。
避免死鎖的技術:
加鎖順序(線程按照一定的順序加鎖)
加鎖時限(線程嘗試擷取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,并釋放自己占有的鎖)
死鎖檢測 (首先為每一個程序和每一個資源指定一個唯一的号碼;而後建立資源配置設定表和程序等待表)
死鎖檢測工具:
Jstack指令:用于列印出給定的java程序ID或core file或遠端調試服務的Java堆棧資訊,生成java虛拟機目前時刻的線程快照。生成線程快照的主要目的是定位線程出現長時間停頓的緣由,如線程間死鎖、死循環、請求外部資源緻使的長時間等待等。 線程出現停頓的時候經過jstack來檢視各個線程的調用堆棧,就能夠知道沒有響應的線程到底在背景作什麼事情,或者等待什麼資源。
JConsole工具:用于連結正在運作的本地或者遠端的JVM,對運作在Java應用程式的資源消耗和性能進行監控,并畫出大量的圖表,提供強大的可視化界面。
什麼是死鎖?鎖等待?如何優化這類問題?通過資料庫哪些表可以監控?
死鎖是指兩個或多個事務在同一資源上互相占用,并請求加鎖時,而導緻的惡性循環現象。當多個事務以不同順序試圖加鎖同一資源時,就會産生死鎖。當多個程序通路同一資料庫時,其中每個程序擁有的鎖都是其他程序所需的,由此造成每個程序都無法繼續下去。InnoDB的并發寫操作會觸發死鎖,InnoDB也提供了死鎖檢測機制,可以通過設定innodb_deadlock_detect參數可以打開或關閉死鎖檢測。打開死鎖檢測資料庫發生死鎖時自動復原(預設選項)。關閉死鎖檢測,發生死鎖的時候,用鎖逾時來處理,通過設定鎖逾時參數innodb_lock_wait_timeout可以在逾時發生時復原被阻塞的事務
鎖等待:mysql資料庫中,不同session在更新同行資料中,會出現鎖等待
重要的三張鎖的監控表innodb_trx,innodb_locks,innodb_lock_waits
如何優化鎖:
1、盡可能讓所有的資料檢索都通過索引來完成,進而避免Innodb因為無法通過索引鍵加鎖而更新為表級鎖定
2、合理設計索引。不經常使用的列最好不加鎖
3、盡可能減少基于範圍的資料檢索過濾條件
總結
以上就是今天要講的内容,還希望各位讀者大大能夠在評論區積極參與讨論,給文章提出一些寶貴的意見或者建議????,合理的内容,我會采納更新博文,重新分享給大家。