溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

mysql出現(xiàn)死鎖的必要條件是什么

發(fā)布時間:2023-05-10 09:56:45 來源:億速云 閱讀:129 作者:zzz 欄目:MySQL數(shù)據(jù)庫

今天小編給大家分享一下mysql出現(xiàn)死鎖的必要條件是什么的相關(guān)知識點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

在mysql中,死鎖指的是在兩個或兩個以上不同的進(jìn)程或線程中,因爭奪資源而造成的一種互相等待的現(xiàn)象;由于存在共同資源的競爭或進(jìn)程(或線程)間的通訊而導(dǎo)致各個線程間相互掛起等待,如果沒有外力作用,最終會引發(fā)整個系統(tǒng)崩潰。mysql出現(xiàn)死鎖的必要條件:1、資源獨(dú)占條件;2、請求和保持條件;3、不剝奪條件;4、相互獲取鎖條件。

1、什么是死鎖?

死鎖指的是在兩個或兩個以上不同的進(jìn)程或線程中,因爭奪資源而造成的一種互相等待的現(xiàn)象;由于存在共同資源的競爭或進(jìn)程(或線程)間的通訊而導(dǎo)致各個線程間相互掛起等待,如果沒有外力作用,最終會引發(fā)整個系統(tǒng)崩潰。

此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等的進(jìn)程稱為死鎖進(jìn)程。

2、Mysql出現(xiàn)死鎖的必要條件

  • 資源獨(dú)占條件

指多個事務(wù)在競爭同一個資源時存在互斥性,即在一段時間內(nèi)某資源只由一個事務(wù)占用,也可叫獨(dú)占資源(如行鎖)。

  • 請求和保持條件

指在一個事務(wù)a中已經(jīng)獲得鎖A,但又提出了新的鎖B請求,而該鎖B已被其它事務(wù)b占有,此時該事務(wù)a則會阻塞,但又對自己已獲得的鎖A保持不放。

  • 不剝奪條件

指一個事務(wù)a中已經(jīng)獲得鎖A,在未提交之前,不能被剝奪,只能在使用完后提交事務(wù)再自己釋放。

  • 相互獲取鎖條件

指在發(fā)生死鎖時,必然存在一個相互獲取鎖過程,即持有鎖A的事務(wù)a在獲取鎖B的同時,持有鎖B的事務(wù)b也在獲取鎖A,最終導(dǎo)致相互獲取而各個事務(wù)都阻塞。

3、 Mysql經(jīng)典死鎖案例

假設(shè)存在一個轉(zhuǎn)賬情景,A賬戶給B賬戶轉(zhuǎn)賬50元的同時,B賬戶也給A賬戶轉(zhuǎn)賬30元,那么在這過程中是否會存在死鎖情況呢?

3.1 建表語句

CREATE TABLE `account` (
  `id` int(11) NOT NULL COMMENT '主鍵',
  `user_id` varchar(56) NOT NULL COMMENT '用戶id',
  `balance` float(10,2) DEFAULT NULL COMMENT '余額',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='賬戶余額表';

3.2 初始化相關(guān)數(shù)據(jù)

INSERT INTO `test`.`account` (`id`, `user_id`, `balance`) VALUES (1, 'A', 80.00);
INSERT INTO `test`.`account` (`id`, `user_id`, `balance`) VALUES (2, 'B', 60.00);

mysql出現(xiàn)死鎖的必要條件是什么

3.3 正常轉(zhuǎn)賬過程

在說死鎖問題之前,咱們先來看看正常的轉(zhuǎn)賬過程。
正常情況下,A用戶給B用戶轉(zhuǎn)賬50元,可在一個事務(wù)內(nèi)完成,需要先獲取A用戶的余額和B用戶的余額,因為之后需要修改這兩條數(shù)據(jù),所以需要通過寫鎖(for UPDATE)鎖住他們,防止其他事務(wù)更改導(dǎo)致我們的更改丟失而引起臟數(shù)據(jù)。
相關(guān)sql如下:

開啟事務(wù)之前需要先把mysql的自動提交關(guān)閉

set autocommit=0;
# 查看事務(wù)自動提交狀態(tài)狀態(tài)

show VARIABLES like 'autocommit';![在這里插入圖片描述](https://img-blog.csdnimg.cn/a486a4ed5c9d4240bd115ac7b3ce5a39.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_

Q1NETiBA6ZqQIOmjjg==,size_20,color_FFFFFF,t_70,g_se,x_16)

# 轉(zhuǎn)賬sql
START TRANSACTION;
# 獲取A 的余額并存入A_balance變量:80
SELECT user_id,@A_balance:=balance from account where user_id = 'A' for UPDATE;
# 獲取B 的余額并存入B_balance變量:60
SELECT user_id,@B_balance:=balance from account where user_id = 'B' for UPDATE;

# 修改A 的余額
UPDATE account set balance = @A_balance - 50 where user_id = 'A';
# 修改B 的余額
UPDATE account set balance = @B_balance + 50 where user_id = 'B';
COMMIT;

執(zhí)行后的結(jié)果:

mysql出現(xiàn)死鎖的必要條件是什么

可以看到數(shù)據(jù)更新都是正常的情況

3.4 死鎖轉(zhuǎn)賬過程

初始化的余額為:

mysql出現(xiàn)死鎖的必要條件是什么

假設(shè)在高并發(fā)情況下存在這種場景,A用戶給B用戶轉(zhuǎn)賬50元的同時,B用戶也給A用戶轉(zhuǎn)賬30元。

那么我們的java程序操作的過程和時間線如下:

1.A用戶給B用戶轉(zhuǎn)賬50元,需在程序中開啟事務(wù)1來執(zhí)行sql,并獲取A的余額同時鎖住A這條數(shù)據(jù)。

# 事務(wù)1
set autocommit=0;
START TRANSACTION;
# 獲取A 的余額并存入A_balance變量:80
SELECT user_id,@A_balance:=balance from account where user_id = 'A' for UPDATE;

2.B用戶給A用戶轉(zhuǎn)賬30元,需在程序中開啟事務(wù)2來執(zhí)行sql,并獲取B的余額同時鎖住B這條數(shù)據(jù)。

# 事務(wù)2
set autocommit=0;
START TRANSACTION;
# 獲取A 的余額并存入A_balance變量:60
SELECT user_id,@A_balance:=balance from account where user_id = 'B' for UPDATE;

3.在事務(wù)1中執(zhí)行剩下的sql

# 獲取B 的余額并存入B_balance變量:60
SELECT user_id,@B_balance:=balance from account where user_id = 'B' for UPDATE;

# 修改A 的余額
UPDATE account set balance = @A_balance - 50 where user_id = 'A';
# 修改B 的余額
UPDATE account set balance = @B_balance + 50 where user_id = 'B';
COMMIT;

mysql出現(xiàn)死鎖的必要條件是什么

可以看到,在事務(wù)1中獲取B數(shù)據(jù)的寫鎖時出現(xiàn)了超時情況。為什么會這樣呢?主要是因為我們在步驟2的時候已經(jīng)在事務(wù)2中獲取到B數(shù)據(jù)的寫鎖了,那么在事務(wù)2提交或回滾前事務(wù)1永遠(yuǎn)都拿不到B數(shù)據(jù)的寫鎖。

4.在事務(wù)2中執(zhí)行剩下的sql

# 獲取A 的余額并存入B_balance變量:60
SELECT user_id,@B_balance:=balance from account where user_id = 'A' for UPDATE;

# 修改B 的余額
UPDATE account set balance = @A_balance - 30 where user_id = 'B';
# 修改A 的余額
UPDATE account set balance = @B_balance + 30 where user_id = 'A';
COMMIT;

mysql出現(xiàn)死鎖的必要條件是什么

同理可得,在事務(wù)2中獲取A數(shù)據(jù)的寫鎖時也出現(xiàn)了超時情況。因為步驟1的時候已經(jīng)在事務(wù)1中獲取到A數(shù)據(jù)的寫鎖了,那么在事務(wù)1提交或回滾前事務(wù)2永遠(yuǎn)都拿不到A數(shù)據(jù)的寫鎖。

5. 為什么會出現(xiàn)這種情況呢?

主要是因為事務(wù)1和事務(wù)2存在相互等待獲取鎖的過程,導(dǎo)致兩個事務(wù)都掛起阻塞,最終拋出獲取鎖超時的異常。

mysql出現(xiàn)死鎖的必要條件是什么

3.5 死鎖導(dǎo)致的問題

眾所周知,數(shù)據(jù)庫的連接資源是很珍貴的,如果一個連接因為事務(wù)阻塞長時間不釋放,那么后面新的請求要執(zhí)行的sql也會排隊等待,越積越多,最終會拖垮整個應(yīng)用。一旦你的應(yīng)用部署在微服務(wù)體系中而又沒有做熔斷處理,由于整個鏈路被阻斷,那么就會引發(fā)雪崩效應(yīng),導(dǎo)致很嚴(yán)重的生產(chǎn)事故。

4、如何解決死鎖問題?

要想解決死鎖問題,我們可以從死鎖的四個必要條件入手。
由于資源獨(dú)占條件和不剝奪條件是鎖本質(zhì)的功能體現(xiàn),無法修改,所以咱們從另外兩個條件嘗試去解決。

4.1 打破請求和保持條件

根據(jù)上面定義可知,出現(xiàn)這個情況是因為事務(wù)1和事務(wù)2同時去競爭鎖A和鎖B,那么我們是否可以保證鎖A和鎖B一次只能被一個事務(wù)競爭和持有呢?
答案是肯定可以的。下面咱們通過偽代碼來看看:

/**
* 事務(wù)1入?yún)?A, B)
* 事務(wù)2入?yún)?B, A)
**/
public void transferAccounts(String userFrom, String userTo) {
     // 獲取分布式鎖
     Lock lock = Redisson.getLock();
     // 開啟事務(wù)
     JDBC.excute("START TRANSACTION;");
     // 執(zhí)行轉(zhuǎn)賬sql
     JDBC.excute("# 獲取A 的余額并存入A_balance變量:80\n" +
             "SELECT user_id,@A_balance:=balance from account where user_id = '" + userFrom + "' for UPDATE;\n" +
             "# 獲取B 的余額并存入B_balance變量:60\n" +
             "SELECT user_id,@B_balance:=balance from account where user_id = '" + userTo + "' for UPDATE;\n" +
             "\n" +
             "# 修改A 的余額\n" +
             "UPDATE account set balance = @A_balance - 50 where user_id = '" + userFrom + "';\n" +
             "# 修改B 的余額\n" +
             "UPDATE account set balance = @B_balance + 50 where user_id = '" + userTo + "';\n");
     // 提交事務(wù)
     JDBC.excute("COMMIT;");
     // 釋放鎖
     lock.unLock();
}

上面的偽代碼顯而易見可以解決死鎖問題,因為所有的事務(wù)都是通過分布式鎖來串行執(zhí)行的。

那么這樣就真的萬事大吉了嗎?

在小流量情況下看起來是沒問題的,但是在高并發(fā)場景下這里將成為整個服務(wù)的性能瓶頸,因為即使你部署了再多的機(jī)器,但由于分布式鎖的原因,你的業(yè)務(wù)也只能串行進(jìn)行,服務(wù)性能并不因為集群部署而提高并發(fā)量,完全無法滿足分布式業(yè)務(wù)下快、準(zhǔn)、穩(wěn)的要求,所以咱們不妨換種方式來看看怎么解決死鎖問題。

4.2 打破相互獲取鎖條件(推薦)

要打破這個條件其實(shí)也很簡單,那就是事務(wù)再獲取鎖的過程中保證順序獲取即可,也就是鎖A始終在鎖B之前獲取。
我們來看看之前的偽代碼怎么優(yōu)化?

/**
* 事務(wù)1入?yún)?A, B)
* 事務(wù)2入?yún)?B, A)
**/
public void transferAccounts(String userFrom, String userTo) {
     // 對用戶A和B進(jìn)行排序,讓userFrom始終為用戶A,userTo始終為用戶B
     if (userFrom.hashCode() > userTo.hashCode()) {
         String tmp = userFrom;
         userFrom = userTo;
         userTo = tmp;
     }
     // 開啟事務(wù)
     JDBC.excute("START TRANSACTION;");
     // 執(zhí)行轉(zhuǎn)賬sql
     JDBC.excute("# 獲取A 的余額并存入A_balance變量:80\n" +
             "SELECT user_id,@A_balance:=balance from account where user_id = '" + userFrom + "' for UPDATE;\n" +
             "# 獲取B 的余額并存入B_balance變量:60\n" +
             "SELECT user_id,@B_balance:=balance from account where user_id = '" + userTo + "' for UPDATE;\n" +
             "\n" +
             "# 修改A 的余額\n" +
             "UPDATE account set balance = @A_balance - 50 where user_id = '" + userFrom + "';\n" +
             "# 修改B 的余額\n" +
             "UPDATE account set balance = @B_balance + 50 where user_id = '" + userTo + "';\n");
     // 提交事務(wù)
     JDBC.excute("COMMIT;");
 }

假設(shè)事務(wù)1的入?yún)?A, B),事務(wù)2入?yún)?B, A),由于我們對兩個用戶參數(shù)進(jìn)行了排序,所以在事務(wù)1中需要先獲取鎖A在獲取鎖B,事務(wù)2也是一樣要先獲取鎖A在獲取鎖B,兩個事務(wù)都是順序獲取鎖,所以也就打破了相互獲取鎖的條件,最終完美解決死鎖問題。

5、 如何預(yù)防死鎖

阻止死鎖的途徑就是避免滿足死鎖條件的情況發(fā)生,為此我們在開發(fā)的過程中需要遵循如下原則:

1.盡量避免并發(fā)的執(zhí)行涉及到修改數(shù)據(jù)的語句。
2.要求每一個事務(wù)一次就將所有要使用到的數(shù)據(jù)全部加鎖,否則就不允許執(zhí)行。
3.預(yù)先規(guī)定一個加鎖順序,所有的事務(wù)都必須按照這個順序?qū)?shù)據(jù)執(zhí)行封鎖。如不同的過程在事務(wù)內(nèi)部對對象的更新執(zhí)行順序應(yīng)盡量保證一致。
4.每個事務(wù)的執(zhí)行時間不可太長,對程序段的事務(wù)可考慮將其分割為幾個事務(wù)。在事務(wù)中不要求輸入,應(yīng)該在事務(wù)之前得到輸入,然后快速執(zhí)行事務(wù)。
5.使用盡可能低的隔離級別。

6.數(shù)據(jù)存儲空間離散法。該方法是指采用各種手段,將邏輯上在一個表中的數(shù)據(jù)分散的若干離散的空間上去,以便改善對表的訪問性能。主要通過將大表按行或者列分解為若干小表,或者按照不同的用戶群兩種方法實(shí)現(xiàn)。
7.編寫應(yīng)用程序,讓進(jìn)程持有鎖的時間盡可能短,這樣其它進(jìn)程就不必花太長的時間等待鎖被釋放。

死鎖的概念:

如果一組進(jìn)程中的每一個進(jìn)程都在等待僅由該組進(jìn)程中的其他進(jìn)程才能引發(fā)的事件,那么改組進(jìn)程是死鎖的。

死鎖的常見表現(xiàn):

死鎖不僅會發(fā)生多個進(jìn)程中,也會發(fā)生在一個進(jìn)程中。
(1)多進(jìn)程死鎖:有進(jìn)程A,進(jìn)程B,進(jìn)程A擁有資源1,需要請求正在被進(jìn)程B占有的資源2。而進(jìn)程B擁有資源2,請求正在被進(jìn)程A戰(zhàn)友的資源1。兩個進(jìn)程都在等待對方釋放資源后請求該資源,而相互僵持,陷入死鎖。
(2)單進(jìn)程死鎖:進(jìn)程A擁有資源1,而它又在請求資源1,而它所請求的資源1必須等待該資源使用完畢得到釋放后才可被請求。這樣,就陷入了自己的死鎖。

產(chǎn)生死鎖的原因:

(1)進(jìn)程推進(jìn)順序不當(dāng)造成死鎖。
(2)競爭不可搶占性資源引起死鎖。
(3)競爭可消耗性資源引起死鎖。

死鎖的四個必要條件(四個條件四者不可缺一):

(1)互斥條件。某段時間內(nèi),一個資源一次只能被一個進(jìn)程訪問。
(2)請求和保持條件。進(jìn)程A已經(jīng)擁有至少一個資源,此時又去申請其他資源,而該資源又正在被進(jìn)程使用,此時請求進(jìn)程阻塞,但對自己已經(jīng)獲得的資源保持不放。
(3)不可搶占資源。進(jìn)程已獲得的資源在未使用完不能被搶占,只能在自己使用完時由自己釋放。
(4)循環(huán)等待序列。存在一個循環(huán)等待序列P0P1P2……Pn,P0請求正在被進(jìn)程P1占有的資源,P1請求正在被P2占有的資源……Pn正在請求被進(jìn)程P0占有的資源。

解除死鎖的兩種方法:

(1)終止(或撤銷)進(jìn)程。終止(或撤銷)系統(tǒng)中的一個或多個死鎖進(jìn)程,直至打破循環(huán)環(huán)路,使系統(tǒng)從死鎖狀態(tài)中解除出來。
(2)搶占資源。從一個或多個進(jìn)程中搶占足夠數(shù)量的資源,分配給死鎖進(jìn)程,以打破死鎖狀態(tài)。

6、死鎖場景

本文死鎖場景皆為工作中遇到(或同事遇到)并解決的死鎖場景,寫這篇文章的目的是整理和分享,歡迎指正和補(bǔ)充,本文死鎖場景包括:

行鎖導(dǎo)致死鎖
gap lock/next keys lock導(dǎo)致死鎖
index merge 導(dǎo)致死鎖
唯一索引沖突導(dǎo)致死鎖

注:以下場景隔離級別均為默認(rèn)的Repeatable Read;

1)行鎖導(dǎo)致死鎖

mysql出現(xiàn)死鎖的必要條件是什么

死鎖原因詳解:

1.兩個事務(wù)執(zhí)行過程時間上有交集,并且過程發(fā)生在兩者提交之前
2.事務(wù)1更新uid=1的記錄,事務(wù)2更新uid=2的記錄,在RR級別,由于uid是唯一索引,因此兩個事務(wù)將分別持有uid=1和2所在行的獨(dú)占鎖
3.事務(wù)1執(zhí)行到第二條更新語句時,發(fā)現(xiàn)uid=2的行被鎖住,進(jìn)入阻塞等待鎖釋放;
4.事務(wù)2執(zhí)行到第二條語句時發(fā)現(xiàn)uid=1的行被鎖,同樣進(jìn)入阻塞
5.兩個事務(wù)互相等待,死鎖產(chǎn)生。

相應(yīng)業(yè)務(wù)案例和解決方案:
該場景常見于事務(wù)中存在for循環(huán)更新某條記錄的情況,死鎖日志顯示lock_mode X locks rec but not gap waiting(即行鎖而非間隙鎖),解決方案:

1.避免循環(huán)更新,優(yōu)化為一條where鎖定要更新的記錄批量更新
2.如果非要循環(huán)更新,嘗試取消事務(wù)(能接受的話),即每一條更新為一個獨(dú)立的事務(wù)

2)gap lock/next keys lock導(dǎo)致死鎖

mysql出現(xiàn)死鎖的必要條件是什么

mysql出現(xiàn)死鎖的必要條件是什么

mysql出現(xiàn)死鎖的必要條件是什么

死鎖原因分析:

1.事務(wù)1執(zhí)行delete age = 27,務(wù)2執(zhí)行delete age = 31,在RR級別,操作條件不是唯一索引時,行鎖會升級為next keys
lock(可以理解為間隙鎖),因此事務(wù)1鎖住了25到27和27到29的區(qū)間,事務(wù)2鎖住了29到31的區(qū)間
2.事務(wù)1執(zhí)行insert age = 30,等待事務(wù)2釋放鎖
3.事務(wù)2執(zhí)行insert age = 28,等待事務(wù)1釋放鎖
4.死鎖產(chǎn)生,死鎖日志顯示lock_mode X locks gap before rec insert intention waiting

解決方案:

1.降低事務(wù)隔離級別到Read Committed,該隔離級別下間隙鎖降級為行鎖,可以減少死鎖發(fā)生的概率
2.避免這種場景- -

3)index merge導(dǎo)致死鎖

t_user結(jié)構(gòu)改造為:

mysql出現(xiàn)死鎖的必要條件是什么

mysql出現(xiàn)死鎖的必要條件是什么

mysql出現(xiàn)死鎖的必要條件是什么

死鎖分析:

1.在符合場景前提的情況下(即表數(shù)據(jù)量較大,index_merge未關(guān)閉),通過explain分析update t_user where zone_id = 1 and uid = 1可以發(fā)現(xiàn)type是index_merge,即會用到zone_id和uid兩個索引
2.上鎖的過程為:

事務(wù)1:
① 鎖住zone_id=1對應(yīng)的間隙鎖: zoneId in (1,2)
② 鎖住索引zone_id=1對應(yīng)的主鍵索引行鎖id = [1,2]
③ 鎖住uid=1對應(yīng)的間隙鎖: uid in (1, 2)
④ 鎖住uid=1對應(yīng)的主鍵索引行鎖: id = [1, 3]

事務(wù)2:
① 鎖住zone_id=2對應(yīng)的間隙鎖: zoneId in (1,2)
② 鎖住索引zone_id=2對應(yīng)的主鍵索引行鎖id = [3,4]
③ 鎖住uid=2對應(yīng)的間隙鎖: uid in (1,2)
④ 鎖住uid=2對應(yīng)的主鍵索引行鎖: id = [2, 4]

1、如果兩個事務(wù)上鎖的順序相反,則有一定的概率出現(xiàn)死鎖。另外,index_merge的形式鎖住了很多不符合條件的行,浪費(fèi)了資源。一般死鎖日志打印的信息為:

lock_mode
 X locks rec but not gap waiting Record lock

解決方案:創(chuàng)建聯(lián)合索引,使執(zhí)行計劃只會用到一個索引。

注:

update table set name = “wea” where col_1 = 1 or col_2 = 2 ;

col_1和col_2為聯(lián)合索引,遵循最左原則col_1會走索引,但col_2會對整個索引進(jìn)行掃描,此時會對整個索引加鎖。

以上就是“mysql出現(xiàn)死鎖的必要條件是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學(xué)習(xí)更多的知識,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI