面試官問我知不知道 MySQL 的鎖,接下來的 15 分鐘讓他另眼相看

前言

咱們知道,數據也是一種供許多用戶共享訪問的資源。java

如何保證數據併發訪問的一致性、有效性,是全部數據庫必須解決的一個問題,鎖的衝突也是影響數據庫併發訪問性能的一個重要因素。mysql

從這一角度來講,鎖對於數據庫而言就顯得尤其重要。本文將帶領你們一塊兒深刻領略Mysql鎖的各類風采。面試

表鎖

表級鎖是mysql鎖中粒度最大的一種鎖,表示當前的操做對整張表加鎖,資源開銷比行鎖少,不會出現死鎖的狀況,可是發生鎖衝突的機率很大。sql

該鎖定機制最大的特色是實現邏輯很是簡單,帶來的系統負面影響最小。數據庫

因此獲取鎖和釋放鎖的速度很快。因爲表級鎖一次會將整個表鎖定,因此能夠很好的避免困擾咱們的死鎖問題。安全

表鎖被大部分的mysql引擎支持,MyISAM和InnoDB都支持表級鎖。架構

MyISAM只是支持表鎖,所以性能相對Innodb來講相對下降,而Innodb也支持表鎖,可是默認的行鎖,並且只有在查詢或者其餘SQL語句經過索引纔會使用行鎖。併發

行鎖

行鎖的是mysql鎖中粒度最小的一種鎖,由於鎖的粒度很小,因此發生資源爭搶的機率也最小,併發性能最大,可是也會形成死鎖,每次加鎖和釋放鎖的開銷也會變大。微服務

目前主要是Innodb使用行鎖,Innodb也是mysql在5.5.5版本以後默認使用的存儲引擎。性能

行鎖按照使用方式也氛圍共享鎖(S鎖或者讀鎖)和排它鎖(X鎖或者寫鎖)

共享鎖(S鎖,讀鎖)

使用說明:若事務A對數據對象1加上S鎖,則事務A能夠讀數據對象1但不能修改,其餘事務只能再對數據對象1加S鎖,而不能加X鎖,直到事務A釋放數據對象1上的S鎖。

這保證了其餘事務能夠讀數據對象1,但在事務A釋放數據對象1上的S鎖以前不能對數據對象1作任何修改。

用法:

select ... lock in share mode;

----共享鎖就是多個事務對於同一數據能夠共享一把鎖,都能訪問到數據,可是隻能讀不能修改。

排它鎖(X鎖,寫鎖)

使用說明:若事務A對數據對象1加上X鎖,事務A能夠讀數據對象1也能夠修改數據對象1,其餘事務不能再對數據對象1加任何鎖,直到事務A釋放數據對象1上的鎖。

這保證了其餘事務在事務A釋放數據對象1上的鎖以前不能再讀取和修改數據對象1。

select ... for update
----排他鎖就是不能與其餘所並存,如一個事務獲取了一個數據行的排他鎖,其餘事務就不能再獲取該行的其餘鎖

意向共享鎖(IS)和意向排它鎖(IX)

釋義:

意向共享鎖(IS):事務想要在得到表中某些記錄的共享鎖,須要在表上先加意向共享鎖。

意向互斥鎖(IX):事務想要在得到表中某些記錄的互斥鎖,須要在表上先加意向互斥鎖。

意向共享鎖和意向排它鎖總稱爲意向鎖。意向鎖的出現是爲了支持Innodb支持多粒度鎖。

首先,意向鎖是表級別鎖。

理由:當咱們須要給一個加表鎖的時候,咱們須要根據意向鎖去判斷表中有沒有數據行被鎖定,以肯定是否能加成功。

若是意向鎖是行鎖,那麼咱們就得遍歷表中全部數據行來判斷。若是意向鎖是表鎖,則咱們直接判斷一次就知道表中是否有數據行被鎖定了。

因此說將意向鎖設置成表級別的鎖的性能比行鎖高的多。

有了意向鎖以後,前面例子中的事務A在申請行鎖(寫鎖)以前,數據庫會自動先給事務A申請表的意向排他鎖。

當事務B去申請表的寫鎖時就會失敗,由於表上有意向排他鎖以後事務B申請表的寫鎖時會被阻塞。

因此,意向鎖的做用就是:

當一個事務在須要獲取資源的鎖定時,若是該資源已經被排他鎖佔用,則數據庫會自動給該事務申請一個該表的意向鎖。若是本身須要一個共享鎖定,就申請一個意向共享鎖。

若是須要的是某行(或者某些行)的排他鎖定,則申請一個意向排他鎖。

樂觀鎖

樂觀鎖不是數據庫自帶的,須要咱們本身去實現。

樂觀鎖是指操做數據庫時(更新操做),想法很樂觀,認爲此次的操做不會致使衝突,在操做數據時,並不進行任何其餘的特殊處理(也就是不加鎖),而在進行更新後,再去判斷是否有衝突了。

一般實現是這樣的:在表中的數據進行操做時(更新),先給數據表加一個版本(version)字段,每操做一次,將那條記錄的版本號加1。

也就是先查詢出那條記錄,獲取出version字段,若是要對那條記錄進行操做(更新),則先判斷此刻version的值是否與剛剛查詢出來時的version的值相等,若是相等,則說明這段期間,沒有其餘程序對其進行操做,則能夠執行更新,將version字段的值加1;

若是更新時發現此刻的version值與剛剛獲取出來的version的值不相等,則說明這段期間已經有其餘程序對其進行操做了,則不進行更新操做。

使用實例:

1. SELECT data AS old_data, version AS old_version FROM …;

2. 根據獲取的數據進行業務操做,獲得new_data和new_version

3. UPDATE SET data = new_data, version = new_version WHERE version = old_version

if (updated row > 0) {
// 樂觀鎖獲取成功,操做完成
} else {
// 樂觀鎖獲取失敗,回滾並重試
}

優勢

從上面的例子能夠看出,樂觀鎖機制避免了長事務中的數據庫加鎖開銷,大大提高了大併發量下的系統總體性能表現。

缺點

樂觀鎖機制每每基於系統中的數據存儲邏輯,所以也具有必定的侷限性,如在上例中,因爲樂觀鎖機制是在咱們的系統中實現,來自外部系統的更新操做不受咱們系統的控制,所以可能會形成髒數據被更新到數據庫中。

在系統設計階段,應該充分考慮到這些狀況出現的可能性,並進行相應調整(如將樂觀鎖策略在數據庫存儲過程當中實現,對外只開放基於此存儲過程的數據更新途徑,而不是將數據庫表直接對外公開)。

總結:讀用樂觀鎖,寫用悲觀鎖。

悲觀鎖

悲觀鎖介紹(引自百科):

悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)。

悲觀鎖的實現:首先實現悲觀鎖時,咱們必須先使用set autocommit=0; 關閉mysql的autoCommit屬性。由於咱們查詢出數據以後就要將該數據鎖定。

關閉自動提交後,咱們須要手動開啓事務。

//1.開始事務
begin; 或者 start transaction;

//2.查詢出商品信息,而後經過for update鎖定數據防止其餘事務修改
select status from t_goods where id=1 for update;

//3.根據商品信息生成訂單
insert into t_orders (id,goods_id) values (null,1);

//4.修改商品status爲2
update t_goods set status=2;

//4.提交事務
commit; --執行完畢,提交事務

上述就實現了悲觀鎖,悲觀鎖就是悲觀主義者,它會認爲咱們在事務A中操做數據1的時候,必定會有事務B來修改數據1,因此,在第2步咱們將數據查詢出來後直接加上排它鎖(X)鎖,防止別的事務來修改事務1,直到咱們commit後,才釋放了排它鎖。

優勢

保證了數據處理時的安全性。

缺點

加鎖形成了開銷增長,而且增長了死鎖的機會。下降了併發性。

樂觀鎖更新有可能會失敗,甚至是更新幾回都失敗,這是有風險的。因此若是寫入居多,對吞吐要求不高,可以使用悲觀鎖。

下面三種鎖都是innodb的行鎖,前面咱們說過行鎖是基於索引實現的,一旦加鎖操做沒有操做在索引上,就會退化成表鎖。

間隙鎖(Next-Key鎖)

間隙鎖,做用於非惟一索引上,主要目的,就是爲了防止其餘事務在間隔中插入數據,以致使「不可重複讀」。

若是把事務的隔離級別降級爲讀提交(Read Committed, RC),間隙鎖則會自動失效。

如圖:(1,4),(4,7),(7,11),(11,∞)即爲間隙鎖要鎖定的位置。

舉例說明:

SELECT * FROM table WHERE id = 8 FOR UPDATE;
----此時,(7,11)就會被鎖定

SELECT * FROM table WHERE id BETWEN 2 AND 5 FOR UPDATE;
----此時,(1,4)和(4,7)就會被鎖定

記錄鎖

記錄鎖,它封鎖索引記錄,做用於惟一索引上,以下圖所示: 

select * from t where id=4 for update;

它會在id=4的索引記錄上加鎖,以阻止其餘事務插入,更新,刪除id=1的這一行。

須要說明的是: select * from t where id=4; 則是快照讀(SnapShot Read),它並不加鎖,不影響其餘事務操做該數據。

臨鍵鎖

臨鍵鎖,做用於非惟一索引上,是記錄鎖與間隙鎖的組合,以下圖所示:

它的封鎖範圍,既包含索引記錄,又包含索引以前的區間,即(負無窮大,1],(2,4],(5,7],(8,11],(12,無窮大]。

在事務A中執行:

UPDATE table SET name = 'javaHuang' WHERE age = 4;
SELECT * FROM table WHERE age = 4 FOR UPDATE;

這兩個語句都會鎖定(2,4],(4,7)這兩個區間。

即, InnoDB 會獲取該記錄行的 臨鍵鎖 ,並同時獲取該記錄行下一個區間的間隙鎖。

臨鍵鎖的出現是爲了innodb在rr隔離級別下,解決幻讀問題(如何解決幻讀問題,後續會出文章詳細解答,也能夠關注公衆號【Java學習部落】,查看)。

若是把事務的隔離級別降級爲RC,臨鍵鎖則也會失效。

死鎖

釋義:死鎖是指兩個或兩個以上事務在執行過程當中因爭搶鎖資源而形成的互相等待的現象

上圖所示,即爲死鎖產生的常規情景。

那麼如何解決死鎖?

1.等待事務超時,主動回滾。

2.進行死鎖檢查,主動回滾某條事務,讓別的事務能繼續走下去。

下面提供一種方法,解決死鎖的狀態:

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
--查看正在被鎖的事務

 

kill trx_mysql_thread_id;
--(上圖trx_mysql_thread_id列的值)

死鎖是一個很複雜的話題,此處只能簡而言之,後續會寫一篇專門講解死鎖的文章。

總結

經過本文,大體瞭解了mysql大部分鎖的功能,做用,實現以及解決方法,我想作爲了一個java開發工程師,瞭解到這個程度應該已經夠了,畢竟咱們不是DBA,否則瞭解太深,搶了DBA的飯碗可就不太好了,開個玩笑,畢竟學無止境。

Spring Cloud 微服務精彩系列

  1. 阿里面試官問我:到底知不知道什麼是Eureka,此次,我沒沉默
  2. 萬字詳解Ribbon架構,針對面試高頻題多角度細說Ribbon
  3. 什麼是Hystrix,阿里技術最終面,遺憾的倒在Hystrix面前!
  4. 2萬字好文全方位深刻學習SpringCloud Fegin,面試不在彷徨
  5. Zuul,據說SpringCloud不許備要我了,但是爲何面試還要每天問我?
  6. 全網最全講解 Spring Cloud Gateway,認真看完這一篇就夠了!

相關文章
相關標籤/搜索