《深入精通Mysql(三)》深入底層Mysql各種鎖機制(面試必問)

java資源庫 發佈 2020-01-02T17:01:14+00:00

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



我們知道,數據也是一種供許多用戶共享訪問的資源。如何保證數據並發訪問的一致性、有效性,是所有資料庫必須解決的一個問題,鎖的衝突也是影響資料庫並發訪問性能的一個重要因素。從這一角度來說,鎖對於資料庫而言就顯得尤為重要。本文將帶領大家一起深入領略Mysql鎖的各種風采。

表鎖

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

該鎖定機制最大的特點是實現邏輯非常簡單,帶來的系統負面影響最小。

所以獲取鎖和釋放鎖的速度很快。由於表級鎖一次會將整個表鎖定,所以可以很好的避免困擾我們的死鎖問題。

表鎖被大部分的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隔離級別下,解決幻讀問題(如何解決幻讀問題,後續會出文章詳細解答,也可以關注公眾號【ToBeTopJavaer】,查看)。

如果把事務的隔離級別降級為RC,臨鍵鎖則也會失效。

死鎖

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

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

那麼如何解決死鎖?

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

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

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

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;--查看正在被鎖的事務複製代碼
kill trx_mysql_thread_id;--(上圖trx_mysql_thread_id列的值)複製代碼

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

總結

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

怒求一波贊

能堅持看到這兒的都是努力學習的人,我們相信,努力奮鬥終將會使我們過上自己想要的生活。

我會努力更新原創乾貨,也會收集一些精品文章,供大家日常學習。不論如何,如果大家覺得在我這兒能學到點東西,在這兒厚著臉皮的向大家求個贊,求個關注,求個分享。我一定不會辜負大家,為大家的學習之路添加更多精彩的文章。

創作不易,堅持不易,大家的支持是我最大的動力,再次謝謝大家。

關鍵字: