自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

【MySQL死鎖終結(jié)者】5分鐘徹底解決數(shù)據(jù)庫(kù)"卡死"難題!

數(shù)據(jù)庫(kù) 其他數(shù)據(jù)庫(kù) MySQL
對(duì)于讀寫(xiě)事務(wù)來(lái)說(shuō),只有在它第一次對(duì)某個(gè)表執(zhí)行增刪改操作時(shí),才會(huì)為這個(gè)事務(wù)分配一個(gè)事務(wù)id,否則是不分配事務(wù) id 的。 有時(shí),雖然我們開(kāi)啟了一個(gè)讀寫(xiě)事務(wù),但是這個(gè)事務(wù)中全是查詢語(yǔ)句,并沒(méi)有執(zhí)行增刪改操作的語(yǔ)句,這也就意味著這個(gè)事務(wù)并不會(huì)被分配一個(gè)事務(wù)id。

1、找到并確定你的死鎖日志

方式1:基于MySQL錯(cuò)誤日志

方式2:基于SHOW ENGINE INNODB STATUS命令查看最近發(fā)生的死鎖日志

方式3:咨詢你的DBA吧!

2、分析你的死鎖日志

  • 確定死鎖發(fā)生的時(shí)間
  • 確定死鎖發(fā)生的順序
  • 確定死鎖發(fā)生的位置以及觸發(fā)的SQL內(nèi)容

3、確定死鎖原因

  • MySQL順序加鎖順序解鎖(公平鎖)
  • 死鎖日志中出現(xiàn)的鎖,不論是等待的鎖,還是持有的,都是每個(gè)事務(wù)已經(jīng)擁有的鎖結(jié)構(gòu)

4、拓展:特殊情況加鎖引起的死鎖

?? 是不是每次看到死鎖日志就頭大?

?? 明明只是簡(jiǎn)單的INSERT操作,數(shù)據(jù)庫(kù)卻神秘"卡死"?

看完本文,讓你3步快速定位死鎖原因!

1、找到并確定你的死鎖日志

方式1:基于MySQL錯(cuò)誤日志

  • 進(jìn)入MySQL
  • 檢查innodb_print_all_deadlocks變量:
SHOW VARIABLES LIKE 'innodb_print_all_deadlocks';
  • 如果innodb_print_all_deadlocks變量的值為OFF,則需要將其設(shè)置為ON以開(kāi)啟死鎖日志。
SET GLOBAL innodb_print_all_deadlocks = 1;
  • 退出MySQL,查看下面的日志
/usr/local/mysql/data/mysqld.local.err

方式2:基于SHOW ENGINE INNODB STATUS命令查看最近發(fā)生的死鎖日志

輸入該命令后,你需要去輸出的信息中找到如下關(guān)鍵字

------------------------ 
LATEST DETECTED DEADLOCK 
------------------------

后面的內(nèi)容即是最近一次發(fā)生的死鎖的內(nèi)容

方式3:咨詢你的DBA吧!

專業(yè)的事情交給專業(yè)的人來(lái)!DBA會(huì)幫你找到最近的死鎖信息的!

2、分析你的死鎖日志

現(xiàn)在我們已經(jīng)拿到了死鎖日志

2025-04-19 13:39:45 0x3079da000
*** (1) TRANSACTION:
TRANSACTION 10047, ACTIVE 10 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 8, OS thread handle 13013229568, query id 113 localhost root updating
DELETE FROM t1 WHERE i = 1
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 44 page no 3 n bits 72 index PRIMARY of table `itsuka`.`t1` trx id 10047 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 00000000273e; asc     >;;
 2: len 7; hex ad000001210110; asc     !  ;;

*** (2) TRANSACTION:
TRANSACTION 10048, ACTIVE 21 sec starting index read
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 6, OS thread handle 13012672512, query id 114 localhost root updating
DELETE FROM t1 WHERE i = 1
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 44 page no 3 n bits 72 index PRIMARY of table `itsuka`.`t1` trx id 10048 lock mode S locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 00000000273e; asc     >;;
 2: len 7; hex ad000001210110; asc     !  ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 44 page no 3 n bits 72 index PRIMARY of table `itsuka`.`t1` trx id 10048 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 00000000273e; asc     >;;
 2: len 7; hex ad000001210110; asc     !  ;;

*** WE ROLL BACK TRANSACTION (1)

通常我們拿到的死鎖日志如上所示

接下來(lái)我們要做的事情包括:

(1)確定死鎖發(fā)生的時(shí)間

2025-04-19 13:39:45 0x3079da000
*** (1) TRANSACTION:

首先,我們基于死鎖日志了解到死鎖發(fā)生在2025-04-19 13:39:45分。再根據(jù)我們的業(yè)務(wù)日志,即可確定本次死鎖的日志內(nèi)容。

(2)確定死鎖發(fā)生的順序

在上面的例子中,發(fā)生死鎖的兩個(gè)事務(wù)分別是10047號(hào)事務(wù) (1) TRANSACTION。

*** (1) TRANSACTION:
TRANSACTION 10047, ACTIVE 10 sec starting index read

發(fā)生死鎖時(shí), (1) TRANSACTION已經(jīng)進(jìn)行了10秒的索引查詢動(dòng)作。

以及10048號(hào)事務(wù)(2) TRANSACTION

*** (2) TRANSACTION:
TRANSACTION 10048, ACTIVE 21 sec starting index read

發(fā)生死鎖時(shí),(2) TRANSACTION已經(jīng)進(jìn)行了21秒的索引查詢動(dòng)作。

我們知道,事務(wù)ID是順序增加的,更大的事務(wù)ID意味著更晚分配事務(wù)ID。

那么有的同學(xué)就有疑問(wèn)了,為什么 (2) TRANSACTION后于 (1) TRANSACTION創(chuàng)建,線程的執(zhí)行時(shí)間卻更長(zhǎng)呢。

那是因?yàn)椋瑢?duì)于讀寫(xiě)事務(wù)來(lái)說(shuō),只有在它第一次對(duì)某個(gè)表執(zhí)行增刪改操作時(shí),才會(huì)為這個(gè)事務(wù)分配一個(gè)事務(wù)id,否則是不分配事務(wù) id 的。 有時(shí),雖然我們開(kāi)啟了一個(gè)讀寫(xiě)事務(wù),但是這個(gè)事務(wù)中全是查詢語(yǔ)句,并沒(méi)有執(zhí)行增刪改操作的語(yǔ)句,這也就意味著這個(gè)事務(wù)并不會(huì)被分配一個(gè)事務(wù)id。 因此,雖然有時(shí)運(yùn)行的時(shí)間長(zhǎng),反而后分配了事務(wù)ID。

基于事務(wù)ID的大小,我們可以確定, (2) TRANSACTION后于 (1) TRANSACTION:分配事務(wù)ID,但是(2) TRANSACTION更早運(yùn)行。

(3)確定死鎖發(fā)生的位置以及觸發(fā)的SQL內(nèi)容

我們現(xiàn)在回到死鎖日志。

MySQL thread id 8, OS thread handle 13013229568, query id 113 localhost root updating
DELETE FROM t1 WHERE i = 1

這兩行提示了死鎖發(fā)生時(shí)當(dāng)前事務(wù)執(zhí)行的sql內(nèi)容。

死鎖發(fā)生時(shí)正在執(zhí)行一條delete語(yǔ)句。

接著往下看。

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 44 page no 3 n bits 72 index PRIMARY of table `itsuka`.`t1` trx id 10047 lock_mode X locks rec but not gap waiting

這里展示了死鎖發(fā)生時(shí)等待的鎖的位置與內(nèi)容 本次死鎖發(fā)生在PRIMARY即主鍵索引行,死鎖的表為‘itsuka’庫(kù)的‘t1’表,并且正在等待一個(gè)‘lock_mode X locks rec but not gap waiting’鎖。

那么‘lock_mode X locks rec but not gap waiting’鎖是一個(gè)什么東西呢?

數(shù)據(jù)庫(kù)中鎖的類型大家都很熟悉,這里就不做介紹。只給出日志中各種鎖對(duì)應(yīng)的關(guān)鍵字:

鎖類型

關(guān)鍵字

記錄鎖(LOCK_REC_NOT_GAP)

lock_mode X locks rec but not gap

間隙鎖(LOCK_GAP)

lock_mode X locks gap before rec

Next-key 鎖(LOCK_ORNIDARY)

lock_mode X

插入意向鎖(LOCK_INSERT_INTENTION)

lock_mode X locks gap before rec insert intention

基于此,我們可以確定,死鎖發(fā)生時(shí),10047號(hào)事務(wù) (1) TRANSACTION,正在等待一個(gè)主鍵索引上的排他記錄鎖

那么等待的鎖的具體內(nèi)容是什么呢,或者說(shuō),他正在等待誰(shuí)呢,我們接著往下看

Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 00000000273e; asc     '>;;
 2: len 7; hex ad000001210110; asc     !  ;;

這一段,表示等待的鎖的具體信息,包括一些行的物理存儲(chǔ)位置信息。

  • 針對(duì)主鍵索引來(lái)著,這里保存的內(nèi)容是

列編號(hào)

內(nèi)容

0

主鍵值

1

事務(wù)ID

2

回滾段指針

3

第二列值

4

第三列值

5

第四列值

.....

以此類推

對(duì)應(yīng)到上面的案例只有0、1、2的原因是,我的測(cè)試表的結(jié)構(gòu)為

CREATE TABLE `t1`(
    `i` int(11) NOT NULL,
    PRIMARY KEY (`i`)
) ENGINE = InnoDB

因?yàn)橹挥兄麈I列,自然就沒(méi)有后續(xù)的其他內(nèi)容。對(duì)于更加復(fù)雜的表,我們也許會(huì)看到類似如下的信息:

imageimage

原理是一樣的,事務(wù)ID 和 回滾段指針 列不需要過(guò)多關(guān)注,這里不展開(kāi)說(shuō)明。

那么,我們?nèi)绾伟哑渲袑?duì)應(yīng)的數(shù)值解析出來(lái)呢。

針對(duì)有符號(hào)數(shù)值型存儲(chǔ),MySQL為了確保正數(shù)的數(shù)值一定大于負(fù)數(shù),因此會(huì)將每一個(gè)數(shù)值拆成單個(gè)字節(jié),再對(duì)最高位字節(jié)(最高的8個(gè)二進(jìn)制位)的最高位與128(1000,0000)進(jìn)行異或操作,相當(dāng)于將正數(shù)和負(fù)數(shù)的符號(hào)位反過(guò)來(lái)。

我們找一條相對(duì)復(fù)雜的日志為例

0: len 8; hex 85558556e13000e1; asc  U V 0  ;;

將16進(jìn)制值85558556e13000e1貼入計(jì)算器

imageimage

可以觀察到最高位二進(jìn)制數(shù)字為1,說(shuō)明在進(jìn)行異或計(jì)算前這一位為0,我們將其高位修改為0。

imageimage

于是我們得到16進(jìn)制數(shù)字0x5558556E13000E1,再將其轉(zhuǎn)為10進(jìn)制。

imageimage

這樣一來(lái)我們就解析到了得到真正存儲(chǔ)的數(shù)據(jù):384359951401746657。

如法炮制,我們同樣得到本次案例中的主鍵數(shù)據(jù)

0: len 4; hex 80000001; asc     ;;

解析后得到1。即,鎖加在了主鍵為1的這一條記錄上

如果是字符類型,只需要按照對(duì)應(yīng)的字符集切分成相同大小的字節(jié)塊,每個(gè)字節(jié)塊單獨(dú)映射即可。

通常針對(duì)字符類型的反算就是deepseek出場(chǎng)的時(shí)候了,但是數(shù)值類型還是我們手動(dòng)來(lái)比較好,deepseek似乎算不太明白。

上面重點(diǎn)講述了主鍵索引在死鎖日志中的日志結(jié)構(gòu),二級(jí)索引結(jié)構(gòu)很類似。

  • 針對(duì)二級(jí)索引來(lái)說(shuō),這里保存的內(nèi)容是

列編號(hào)

內(nèi)容

0

二級(jí)索引列1

1

二級(jí)索引列2

2

二級(jí)索引列3

.....

以此類推

最后一行

主鍵值

現(xiàn)在回到案例上來(lái),我們現(xiàn)在已經(jīng)確定了,事務(wù)1的死鎖發(fā)生在‘itsuka’庫(kù)的‘t1’表的主鍵索引上,死鎖發(fā)生時(shí)正在等待自己的排他記錄鎖的獲取,鎖的位置位于主鍵索引上主鍵為1(80000001)那條記錄

事務(wù)2的死鎖日志大部分與事務(wù)1相同,只是多了如下內(nèi)容

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 44 page no 3 n bits 72 index PRIMARY of table `itsuka`.`t1` trx id 10048 lock mode S locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 00000000273e; asc     '>;;
 2: len 7; hex ad000001210110; asc     !  ;;

這段表明,死鎖發(fā)生時(shí),事務(wù)2已經(jīng)持有一把鎖,鎖的類型是共享記錄鎖,鎖的位置為主鍵索引上主鍵為1

( 0: len 4; hex 80000001; asc     ;;)

的那條記錄。

同時(shí),事務(wù)2的死鎖發(fā)生時(shí)還在等待自己的排他記錄鎖的獲取,鎖的位置位于主鍵索引上主鍵為1

( 0: len 4; hex 80000001; asc     ;;)

的那條記錄

至此,我們的死鎖日志就分析就全部結(jié)束了。

3、確定死鎖原因

基于上一節(jié)我們知道了,死鎖發(fā)生時(shí)我們收集到了兩個(gè)事務(wù)的信息。

事務(wù)1的事物ID為10047,事務(wù)執(zhí)行的時(shí)間較短為10秒鐘,死鎖時(shí)正在等待獲取一把獨(dú)占型記錄鎖,這把鎖加在了‘itsuka’庫(kù)的‘t1’表的主鍵索引上主鍵為1的那條記錄上。

事務(wù)2的事物ID為10048,事務(wù)執(zhí)行的時(shí)間較長(zhǎng)為21秒鐘,死鎖時(shí)正持有一把共享型記錄鎖,這把鎖加在了‘itsuka’庫(kù)的‘t1’表的主鍵索引上主鍵為1的那條記錄上,同時(shí)事務(wù)2又嘗試獲取一把獨(dú)占型記錄鎖,這把鎖加在了‘itsuka’庫(kù)的‘t1’表的主鍵索引上主鍵為1的那條記錄上。

在開(kāi)始我們下一階段的思考之前,我們需要明確幾個(gè)問(wèn)題。

(1)InnoDB順序加鎖順序解鎖(公平鎖)

imageimage

基于MySQL的官方文檔,我們可以知道。一個(gè)事務(wù)成功加鎖的前提是:這條記錄的鎖等待隊(duì)列中,當(dāng)前事務(wù)前面所有不兼容的加鎖請(qǐng)求都已釋放(提交或回滾)

(2)死鎖日志中出現(xiàn)的鎖,不論是等待的鎖,還是持有的,都是每個(gè)事務(wù)已經(jīng)擁有的鎖結(jié)構(gòu)

有別于java中的鎖,例如ReentrantLock。不管有幾個(gè)線程來(lái)爭(zhēng)搶這把鎖,自始至終都只有一個(gè)鎖結(jié)構(gòu),拿到鎖的線程擁有這把鎖,沒(méi)拿到鎖的線程不擁有這把鎖。

而MySQL每次加鎖都會(huì)在內(nèi)存中生成一個(gè)獨(dú)屬于這個(gè)事務(wù)的鎖結(jié)構(gòu),只不過(guò)鎖結(jié)構(gòu)里有一個(gè)等待狀態(tài)的標(biāo)志,表示這個(gè)鎖獲取成功還是失敗。

在進(jìn)行MySQL的加鎖分析時(shí),一定要明白,不論當(dāng)前事務(wù)有沒(méi)有成功獲取到鎖,都已經(jīng)建立了鎖結(jié)構(gòu)。

因此,上述案例中的鎖結(jié)構(gòu)示意圖如下所示。

imageimage

接下來(lái)我們要做的就是逐個(gè)分析鎖結(jié)構(gòu),判斷他的來(lái)源以及為什么沒(méi)能成功獲取。

所以,該案例的死鎖原因就找到了:

T2事務(wù)先獲取了了S型記錄鎖T1事務(wù)再嘗試獲取X型記錄鎖,與S型記錄鎖沖突,因此T1事務(wù)陷入等待。此時(shí)T2事務(wù)再嘗試獲取這條記錄的X型記錄鎖,根據(jù)請(qǐng)求鎖的原則:這條記錄的鎖等待隊(duì)列中,與T2事務(wù)的加鎖請(qǐng)求沖突的鎖都已釋放(提交或回滾),T2事務(wù)才能加鎖成功。因此T2事務(wù)也陷入等待,并且T2事務(wù)需要等待T1事務(wù)先獲取鎖,但T1事務(wù)要等待T2事務(wù)的S型記錄鎖釋放,死鎖因此產(chǎn)生。

再結(jié)合我們的sql,我們就可以完整還原現(xiàn)場(chǎng):

時(shí)間

事務(wù)1

事務(wù)2

T1


begin;select * from t1 where i=1 LOCK IN SHARE MODE;

T2

begin;DELETE FROM t1 WHERE i = 1;


T3


DELETE FROM t1 WHERE i = 1;

4、拓展:特殊情況加鎖引起的死鎖

當(dāng)然,很多時(shí)候死鎖的產(chǎn)生并不完全是由兩條 SQL 顯式加鎖導(dǎo)致的。MySQL 可能會(huì)背著我們偷偷的加一些鎖,從而引發(fā)死鎖。但是不論是如何加鎖,我們都要先找到死鎖發(fā)生時(shí),每個(gè)事務(wù)都涉及到了哪些鎖結(jié)構(gòu),這些鎖加在了哪里。然后再逐個(gè)分析,或者說(shuō)‘猜’,這些鎖是如何產(chǎn)生的。

例如,當(dāng)MySQL在插入或者更新記錄時(shí)出現(xiàn)唯一鍵沖突,那么會(huì)對(duì)重復(fù)的key加S類型的next-key鎖。因?yàn)閷?duì)于 MySQL 來(lái)說(shuō),不能直接報(bào)錯(cuò),要先檢查當(dāng)前沖突記錄是否為有效記錄,如果發(fā)現(xiàn)沖突的記錄被標(biāo)記刪除了,說(shuō)明他不是有效記錄,新紀(jì)錄可以插入,否則要報(bào)錯(cuò)。為了防止其它事務(wù)更新或者刪除這條記錄、或者往這條記錄前面的間隙里插入記錄,開(kāi)始檢查工作之前,MySQL 會(huì)對(duì)這條記錄加共享鎖。

而當(dāng)insert 語(yǔ)句帶上on duplicate key update這個(gè)小尾巴時(shí),這個(gè)小尾巴的作用是發(fā)現(xiàn)沖突記錄時(shí)執(zhí)行更新操作,既然是更新操作則需要加排他鎖,所以這種情況下發(fā)生唯一鍵沖突,就直接加排他鎖。

更多加鎖情況這里不展開(kāi)講,大家可以自行查閱官方文檔,針對(duì)每種加鎖場(chǎng)景都有明確的描述:https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html

關(guān)于作者

黃敬乾   俠客匯Java開(kāi)發(fā)工程師

責(zé)任編輯:武曉燕 來(lái)源: 轉(zhuǎn)轉(zhuǎn)技術(shù)
相關(guān)推薦

2021-08-28 09:04:54

死鎖順序鎖輪詢鎖

2015-12-09 10:41:51

2021-05-18 09:06:19

零信任郵件安全安全威脅

2021-02-18 08:22:26

KubernetesDocker鏡像

2012-09-10 09:28:51

2009-11-20 18:08:37

Oracle數(shù)據(jù)庫(kù)

2015-07-21 20:49:14

浪潮

2018-05-06 16:52:51

2011-11-28 10:03:29

HTML5移動(dòng)應(yīng)用

2019-11-20 10:38:59

MySQLSQL數(shù)據(jù)庫(kù)

2017-03-02 11:49:51

OPPO

2009-12-03 16:33:02

路由交換設(shè)備

2018-06-26 09:37:07

時(shí)序數(shù)據(jù)庫(kù)FacebookNoSQL

2009-11-02 18:07:58

Oracle數(shù)據(jù)庫(kù)

2021-06-18 07:34:12

Kafka中間件微服務(wù)

2011-09-06 14:36:34

觸摸菜單ipad應(yīng)用電子點(diǎn)菜

2013-11-15 10:15:55

HA系統(tǒng)張振倫HypervisorH

2024-12-04 16:12:31

2016-12-21 15:08:14

數(shù)據(jù)庫(kù)垂直拆分

2020-07-21 07:42:29

數(shù)據(jù)庫(kù)信息技術(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)