久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

MySQL死鎖是什么及怎么掌握

165次閱讀
沒有評論

共計 5354 個字符,預計需要花費 14 分鐘才能閱讀完成。

這篇“MySQL 死鎖是什么及怎么掌握”文章的知識點大部分人都不太理解,所以丸趣 TV 小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“MySQL 死鎖是什么及怎么掌握”文章吧。

1、什么是死鎖?

死鎖指的是在兩個或兩個以上不同的進程或線程中,由于存在共同資源的競爭或進程(或線程)間的通訊而導致各個線程間相互掛起等待,如果沒有外力作用,最終會引發整個系統崩潰。

2、Mysql 出現死鎖的必要條件

資源獨占條件

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

請求和保持條件

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

不剝奪條件

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

相互獲取鎖條件

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

3、Mysql 經典死鎖案例

假設存在一個轉賬情景,A 賬戶給 B 賬戶轉賬 50 元的同時,B 賬戶也給 A 賬戶轉賬 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 初始化相關數據

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);

3.3 正常轉賬過程

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

開啟事務之前需要先把 mysql 的自動提交關閉

set autocommit=0;
#  查看事務自動提交狀態狀態 

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)

#  轉賬 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;

執行后的結果:

可以看到數據更新都是正常的情況

3.4 死鎖轉賬過程

初始化的余額為:

假設在高并發情況下存在這種場景,A 用戶給 B 用戶轉賬 50 元的同時,B 用戶也給 A 用戶轉賬 30 元。

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

1、A 用戶給 B 用戶轉賬 50 元,需在程序中開啟事務 1 來執行 sql,并獲取 A 的余額同時鎖住 A 這條數據。

#  事務 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 用戶轉賬 30 元,需在程序中開啟事務 2 來執行 sql,并獲取 B 的余額同時鎖住 B 這條數據。

#  事務 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、在事務 1 中執行剩下的 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;

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

4、在事務 2 中執行剩下的 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;

同理可得,在事務 2 中獲取 A 數據的寫鎖時也出現了超時情況。因為步驟 1 的時候已經在事務 1 中獲取到 A 數據的寫鎖了,那么在事務 1 提交或回滾前事務 2 永遠都拿不到 A 數據的寫鎖。

5、為什么會出現這種情況呢?

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

3.5 死鎖導致的問題

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

4、如何解決死鎖問題?

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

4.1 打破請求和保持條件

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

/**
*  事務 1 入參 (A, B)
*  事務 2 入參 (B, A)
public void transferAccounts(String userFrom, String userTo) {
 //  獲取分布式鎖
 Lock lock = Redisson.getLock();
 //  開啟事務
 JDBC.excute( START TRANSACTION; 
 //  執行轉賬 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 
 //  提交事務
 JDBC.excute( COMMIT; 
 //  釋放鎖
 lock.unLock();}

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

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

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

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

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

/**
*  事務 1 入參 (A, B)
*  事務 2 入參 (B, A)
public void transferAccounts(String userFrom, String userTo) {
 //  對用戶 A 和 B 進行排序,讓 userFrom 始終為用戶 A,userTo 始終為用戶 B
 if (userFrom.hashCode()   userTo.hashCode()) {
 String tmp = userFrom;
 userFrom = userTo;
 userTo = tmp;
 }
 //  開啟事務
 JDBC.excute( START TRANSACTION; 
 //  執行轉賬 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 
 //  提交事務
 JDBC.excute( COMMIT; 
 }

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

以上就是關于“MySQL 死鎖是什么及怎么掌握”這篇文章的內容,相信大家都有了一定的了解,希望丸趣 TV 小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注丸趣 TV 行業資訊頻道。

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-07-13發表,共計5354字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 宜章县| 宽城| 莱芜市| 眉山市| 洛阳市| 区。| 都江堰市| 孝义市| 西华县| 南漳县| 利辛县| 虞城县| 孟州市| 阳谷县| 赞皇县| 滦平县| 开鲁县| 伊吾县| 天津市| 渝中区| 大悟县| 健康| 怀远县| 太白县| 舟山市| 腾冲县| 曲沃县| 泰州市| 思南县| 邯郸县| 宝山区| 东莞市| 凤冈县| 曲松县| 定日县| 耒阳市| 大渡口区| 吴忠市| 秦安县| 贵阳市| 应城市|