一文詳解Mysql?insert也會(huì)發(fā)生死鎖嗎
前言
今天給大家分享我們前幾天線(xiàn)上遇到的一個(gè)Mysql死鎖的案列,希望在大家碰到類(lèi)似的問(wèn)題時(shí)有所幫助。
9月28號(hào)下午我們線(xiàn)上釘釘報(bào)警群報(bào)了一個(gè)“Error 1213: Deadlock found when trying to get lock”的錯(cuò)誤,第一次線(xiàn)上發(fā)生數(shù)據(jù)庫(kù)死鎖,當(dāng)時(shí)感覺(jué)事態(tài)嚴(yán)重。
來(lái)不急多想,馬上通過(guò)錯(cuò)誤日志堆棧找到了發(fā)生死鎖的sql語(yǔ)句,竟然是一條insert語(yǔ)句:“insert into ... on duplicate key update ...”,這直接戳中了我的盲區(qū):insert也會(huì)導(dǎo)致死鎖?
在正式介紹案例前我們先來(lái)看一下前置知識(shí),這有助于后面的理解。
前置知識(shí)
記錄鎖
包含共享鎖和獨(dú)占鎖
共享鎖:簡(jiǎn)稱(chēng)S鎖,當(dāng)事務(wù)讀取一條記錄時(shí)需要先獲取改記錄的S鎖,如果一條記錄持有S鎖,其他事務(wù)可以繼續(xù)獲取該記錄的S鎖,但不能獲取X鎖。
獨(dú)占鎖:也成排他鎖,簡(jiǎn)稱(chēng)X鎖,如果一條記錄持有X 鎖,其他事務(wù)既不可以獲取該記錄的S鎖,也不能獲取該記錄的X鎖。
間隙鎖,簡(jiǎn)稱(chēng)gap鎖
一種在記錄前面添加的鎖,該鎖阻止新記錄插入到當(dāng)前記錄的前面,直到當(dāng)前記錄的間隙鎖釋放后新記錄才能正常插入。next-key鎖
本質(zhì)是記錄鎖+間隙鎖的組合插入意向鎖
新記錄在被間隙鎖阻塞時(shí)會(huì)生成插入意向鎖,間隙鎖釋放后插入意向鎖也會(huì)釋放隱式鎖
Mysql 為了節(jié)省鎖的開(kāi)銷(xiāo),insert語(yǔ)句執(zhí)行時(shí)記錄是不會(huì)生成鎖的,只有在滿(mǎn)足下面條件時(shí)insert語(yǔ)句執(zhí)行后的記錄才會(huì)生成鎖:
當(dāng)其他事務(wù)想獲取該記錄的S鎖或X鎖時(shí)且該記錄所在的聚簇索引中的事務(wù)屬于活躍狀態(tài)時(shí)(1、每條記錄的聚簇索引中會(huì)有一個(gè)隱藏字段存儲(chǔ)該記錄被最后修改時(shí)所在的事務(wù)id 2、已開(kāi)始但未commit的事務(wù)稱(chēng)為活躍的事務(wù)),在其他事務(wù)中會(huì)為該事務(wù)(指的是記錄所在的聚簇索引中存儲(chǔ)的事務(wù))生成X鎖,并將其置為not waitting(持有)狀態(tài),而將自己的鎖狀態(tài)標(biāo)記為waitting(阻塞)狀態(tài)。(這段比較難理解,不要著急,后面會(huì)通過(guò)案例詳細(xì)說(shuō)明)
而其他情況則可以正常讀取,不需要生成鎖。
我們將insert時(shí)不生成鎖,等到滿(mǎn)足條件時(shí)才生成的鎖稱(chēng)為隱式鎖,從這里可以看出隱式鎖實(shí)際上不是一種新鎖,而是一種特殊的記錄鎖。
對(duì)于insert語(yǔ)句當(dāng)遇到唯一二級(jí)索引重復(fù)時(shí)無(wú)論事務(wù)處于什么隔離級(jí)別都會(huì)為記錄添加S型鎖和next-key鎖,而對(duì)于insert...on duplicate key...這樣的語(yǔ)句當(dāng)遇到唯一二級(jí)索引重復(fù)時(shí)無(wú)論事務(wù)處于什么隔離級(jí)別都會(huì)為記錄添加X(jué)型鎖和next-key鎖
(主鍵重復(fù)的場(chǎng)景這里不做介紹,在后面的推薦資料中大家可以自行了解)
所有的鎖在內(nèi)存中都表現(xiàn)為一個(gè)鎖結(jié)構(gòu),鎖結(jié)構(gòu)中有一個(gè)等待的屬性,如果為true,表示當(dāng)前事務(wù)獲取到鎖成功,如果為false,表示當(dāng)前事務(wù)尚未獲取到鎖,處于等待狀態(tài)。
死鎖分析
接著排查問(wèn)題,通過(guò)SHOW ENGINE INNODB STATUS語(yǔ)句查事務(wù)加鎖的日志,里面就有最近一次的死鎖記錄。(因?yàn)閿?shù)據(jù)的敏感性和便于分析,我將數(shù)據(jù)做了替換、刪除了對(duì)分析無(wú)關(guān)的字段)
SHOW ENGINE INNODB STATUS只會(huì)顯示最后一次死鎖日志,如果要顯示所有發(fā)生的死鎖日志則需要將系統(tǒng)變量:innodb_print_all_deadlocks設(shè)置為ON
下面為事務(wù)的死鎖日志,其中標(biāo)注的①②③④⑤⑥為6個(gè)關(guān)鍵點(diǎn)
------------------------ LATEST DETECTED DEADLOCK ------------------------ 2023-09-28 14:56:20 0x7fb14a2bd700 *** (1) TRANSACTION: TRANSACTION 1374635254, ACTIVE 0 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1 MySQL thread id 2045802, OS thread handle 140399504230144, query id 12689481084 192.168.0.1 account_001 update ①發(fā)生死鎖時(shí)此事務(wù)正在執(zhí)行的語(yǔ)句 insert into course_member_statics(course_id,uid) values('20230928145601000001',222222) on duplicate key update member_delete_flag=0 ②此事務(wù)正在等待其他事務(wù)對(duì)記錄course_id:20230928145601000001、uid:222222釋放X型記錄鎖 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 1753 page no 659149 n bits 360 index idx_courseid_uid of table `uclass`.`course_member_statics` trx id 1374635254 lock_mode X waiting Record lock, heap no 58 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 22; hex 3230323330393238313435363031303030303031; asc 20230928145601000001;; # 3230323330393238313435363031303030303031是20230928145601000001的utf8編碼,這里是course_id字段的值 1: len 4; hex 0003640E; asc GD ;;# 0003640E是222222十六進(jìn)制編碼,這里是uid字段的值【下同】 2: len 8; hex 8000000000a66c9d; asc l ;; # 8000000000a66c9d是10906781十六進(jìn)制編碼,這里是主鍵id字段的值(存儲(chǔ)的是有符號(hào)數(shù),前面的8要改成0)【下同】 *** (2) TRANSACTION: TRANSACTION 1374634984, ACTIVE 0 sec inserting mysql tables in use 1, locked 1 15 lock struct(s), heap size 1136, 160 row lock(s), undo log entries 669 MySQL thread id 2045822, OS thread handle 140399430326016, query id 12689481315 192.168.0.2 account_001 update ③發(fā)生死鎖時(shí)此事務(wù)正在執(zhí)行的語(yǔ)句 insert ignore into course_member_statics(course_id,uid) values ('20230928145601000001',222222), ('20230928145601000001',111111) *** (2) HOLDS THE LOCK(S): ④此事務(wù)對(duì)記錄course_id:20230928145601000001、uid:222222持有X型記錄鎖 RECORD LOCKS space id 1753 page no 659149 n bits 312 index idx_courseid_uid of table `uclass`.`course_member_statics` trx id 1374634984 lock_mode X locks rec but not gap Record lock, heap no 38 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 22; hex 3230323330393238313435363031303030303031; asc 20230928145601000001;; 1: len 4; hex 0003640E; asc ;; 2: len 8; hex 8000000000a66c9d; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: ⑤此事務(wù)對(duì)記錄course_id:20230928145601000001、uid:222222持有插入意向鎖,正在等待其他事務(wù)對(duì)該記錄釋放間隙鎖 RECORD LOCKS space id 1753 page no 659149 n bits 472 index idx_courseid_uid of table `uclass`.`course_member_statics` trx id 1374634984 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 58 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 22; hex 3230323330393238313435363031303030303031; asc 20230928145601000001;; 1: len 4; hex 0003640E; asc GD ;; 2: len 8; hex 8000000000a66c9d; asc ;; ⑥最后決定回滾事務(wù)1 *** WE ROLL BACK TRANSACTION (1)
我們從上述日志中摘取下面幾個(gè)關(guān)鍵信息進(jìn)行說(shuō)明:
LATEST DETECTED DEADLOCK:表示最新檢測(cè)到的死鎖,下方為死鎖的事務(wù)日志
(x) TRANSACTION:表示第幾個(gè)事務(wù),(1) TRANSACTION為第一個(gè),(2) TRANSACTION為第二個(gè)
- WAITING FOR THIS LOCK TO BE GRANTED:表示當(dāng)前事務(wù)正在對(duì)某條記錄加某種類(lèi)型的鎖,但由于其他事務(wù)已經(jīng)對(duì)該記錄持有某種類(lèi)型的鎖而導(dǎo)致阻塞,自己處于等待狀態(tài)中,一旦其他事務(wù)釋放鎖,該事務(wù)就可以加鎖成功。
RECORD LOCKS...:表示要添加的、處于阻塞中的鎖,其中l(wèi)ock_mode X waiting表示正在等待加X(jué)型next-key鎖,lock_mode X locks gap before rec insert intention waiting表示想在某條記錄前面插入記錄,由于該記錄持有間隙鎖,正在等待間隙鎖釋放,此時(shí)持有插入意向鎖
Record lock:表示要加的、處于等待中的鎖作用在哪些記錄上,可能會(huì)有多條。其下方的hex中數(shù)據(jù)為編碼后的數(shù)據(jù),如果真實(shí)數(shù)據(jù)為字符串則編碼格式為十六進(jìn)制uft8,如果真實(shí)數(shù)據(jù)為整形則編碼格式為十六進(jìn)制,我們可以將其解碼得到真實(shí)的數(shù)據(jù)。(下同)
通過(guò)解碼后的數(shù)據(jù)我們就能知道鎖作用于哪些記錄了,這對(duì)我們分析死鎖是非常有用的。
- HOLDS THE LOCK(S):表示當(dāng)前事務(wù)持有哪些鎖
RECORD LOCKS...:表示已經(jīng)持有的鎖,其中l(wèi)ock_mode X locks rec but not gap表示持有記錄的X型記錄鎖,不持有間隙鎖
Record lock:表示持有的鎖作用在哪些記錄上,可能會(huì)有多條。
在(x) TRANSACTION下方和WAITING FOR THIS LOCK TO BE GRANTED或HOLDS THE LOCK(S)上方之間出現(xiàn)的sql語(yǔ)句為導(dǎo)致出現(xiàn)死鎖的sql語(yǔ)句,像日志中標(biāo)出的①和③就是導(dǎo)致死鎖的sql語(yǔ)句
WE ROLL BACK TRANSACTION (1):表示死鎖發(fā)生時(shí)回滾哪個(gè)事務(wù),這里回滾的是第一個(gè)事務(wù),Mysql會(huì)將受影響的數(shù)據(jù)最少的事務(wù)回滾
下面我們對(duì)這次死鎖做一次完整的分析:
通過(guò)日志可以知道,
事務(wù)1執(zhí)行的語(yǔ)句為:
insert into course_member_statics(course_id,uid) values('20230928145601000001',222222) on duplicate key update member_delete_flag=0;
事務(wù)2執(zhí)行的語(yǔ)句為:
insert ignore into course_member_statics(course_id,uid) values ('20230928145601000001',222222), ('20230928145601000001',111111);
其中course_id和uid為唯一索引。
1、事務(wù)2執(zhí)行插入222222這條數(shù)據(jù)
insert ignore into course_member_statics(course_id,uid) values ('20230928145601000001',222222);
(這里怎么是單條insert,不是批量insert嗎?雖然sql語(yǔ)法是批量insert但實(shí)際到存儲(chǔ)引擎執(zhí)行的時(shí)候是一條條進(jìn)行的),因?yàn)槭瞧胀ǖ膇nsert語(yǔ)句所以不會(huì)加鎖
2、事務(wù)1執(zhí)行
insert into course_member_statics(course_id,uid) values('20230928145601000001',222222) on duplicate key update member_delete_flag=0;
發(fā)現(xiàn)事務(wù)2已經(jīng)插入了一個(gè)相同的記錄,于是事務(wù)1要對(duì)該記錄添加X(jué)型next-key鎖。
3、根據(jù)前面的知識(shí)我們知道,對(duì)一條insert的數(shù)據(jù),如果其他事務(wù)要對(duì)其加S型或X型鎖,且該記錄對(duì)應(yīng)的聚簇索引中存儲(chǔ)的事務(wù)id處于活躍狀態(tài)時(shí),就會(huì)觸發(fā)這條記錄上的隱式鎖升級(jí)為顯示鎖。
在這里就是事務(wù)1給事務(wù)2在222222記錄增加X(jué)型記錄鎖,并將其狀態(tài)置于持有狀態(tài),同時(shí)將自己置于阻塞狀態(tài)
4、事務(wù)2執(zhí)行插入111111這條數(shù)據(jù)
insert ignore into course_member_statics(course_id,uid) values ('20230928145601000001',111111);
按照二級(jí)索引存儲(chǔ)的特點(diǎn),記錄111111要插在記錄222222的前面,這時(shí)出現(xiàn)了插入意向鎖阻塞,按照我們前面的說(shuō)的,在某條記錄前面插入數(shù)據(jù)只有在該記錄持有間隙鎖時(shí)才會(huì)阻塞,問(wèn)題是事務(wù)1對(duì)記錄222222并沒(méi)有持有間隙鎖,怎么會(huì)阻塞呢?
Mysql規(guī)定,只要?jiǎng)e的事務(wù)對(duì)記錄生成了一個(gè)顯式的間隙鎖的鎖結(jié)構(gòu),不論那個(gè)事務(wù)是已經(jīng)獲取到了該鎖(granted),還是正在等待獲取(waiting),當(dāng)前事務(wù)要在該記錄前面插入新記錄都會(huì)被阻塞。
回到該例,因?yàn)槭聞?wù)1已經(jīng)為記錄222222生成了一個(gè)X型的next-key鎖結(jié)構(gòu)(next-key鎖包含間隙鎖),雖然該鎖的狀態(tài)是在阻塞等待中,但事務(wù)2在該記錄前插入記錄仍然會(huì)被阻塞。
這時(shí)事務(wù)1在等待事務(wù)2釋放記錄222222上的X型記錄鎖,同時(shí)事務(wù)2也在等待事務(wù)1在記錄222222上的間隙鎖釋放,出現(xiàn)了互相等待的現(xiàn)象,導(dǎo)致了死鎖發(fā)生。
最后由于死鎖導(dǎo)致事務(wù)1被回滾了,事務(wù)2執(zhí)行成功,因?yàn)槭聞?wù)2包含事務(wù)1的數(shù)據(jù),所有沒(méi)有對(duì)線(xiàn)上的數(shù)據(jù)造成影響,就算最后回滾的是事務(wù)2也沒(méi)問(wèn)題,因?yàn)閕nsert ignore into語(yǔ)句代碼做了錯(cuò)誤重試處理。
下面我們通過(guò)例子還原上述死鎖,并對(duì)每條sql語(yǔ)句的執(zhí)行進(jìn)行加鎖分析
還原死鎖
建表sql語(yǔ)句
DROP TABLE IF EXISTS `course_member_statics`; CREATE TABLE `course_member_statics` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', `course_id` varchar(40) NOT NULL DEFAULT '' COMMENT '課程ID', `uid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用戶(hù)UID', `delete_flag` tinyint(2) NOT NULL DEFAULT '0' COMMENT '是否被刪除 狀態(tài) 0:未刪除 1:已刪除', PRIMARY KEY (`id`), UNIQUE KEY `idx_courseid_uid` (`course_id`,`uid`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='課程成員表';
事務(wù)1要執(zhí)行的語(yǔ)句:
START TRANSACTION; a、insert into course_member_statics(course_id,uid) values ('20230928145601000001',222222) on duplicate key update delete_flag=0; COMMIT;
事務(wù)2要執(zhí)行的語(yǔ)句:(為了每次都出現(xiàn)死鎖,這里將批量插入改成了單獨(dú)的兩條insert)
START TRANSACTION; b、insert ignore into course_member_statics(course_id,uid) values ('20230928145601000001',222222); c、insert ignore into course_member_statics(course_id,uid) values ('20230928145601000001',111111); COMMIT;
我們按照b,a,c的順序逐步在終端執(zhí)行(事務(wù)開(kāi)始前最好要先執(zhí)行START TRANSACTION語(yǔ)句,如果不執(zhí)行同時(shí)系統(tǒng)變量autocommit=ON時(shí)每執(zhí)行一條sql都會(huì)認(rèn)為是一個(gè)單獨(dú)的事務(wù),無(wú)法看到死鎖效果),
并通過(guò)SHOW ENGINE INNODB STATUS來(lái)查看加鎖情況(注意:開(kāi)始執(zhí)行sql語(yǔ)句前還需要將系統(tǒng)變量innodb_status_output_locks打開(kāi)(set GLOBAL innodb_status_output_locks = 1),否則日志中不會(huì)出現(xiàn)任何加鎖信息)
1、先執(zhí)行事務(wù)2的b語(yǔ)句,執(zhí)行SHOW ENGINE INNODB STATUS看日志
------------ TRANSACTIONS ------------ ---TRANSACTION 1864, ACTIVE 5 sec 1 lock struct(s), heap size 1128, 0 row lock(s), undo log entries 1 MySQL thread id 22, OS thread handle 6129594368, query id 125 localhost 127.0.0.1 root TABLE LOCK table `test`.`course_member_statics` trx id 1864 lock mode IX
看TRANSACTIONS段落,可以看出語(yǔ)句執(zhí)行完后事務(wù)2只持有表的意向X型鎖,沒(méi)有持有記錄的任何鎖
2、再執(zhí)行事務(wù)1的a語(yǔ)句,執(zhí)行SHOW ENGINE INNODB STATUS看日志
------------ TRANSACTIONS ------------ ---TRANSACTION 1865, ACTIVE 20 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1128, 1 row lock(s), undo log entries 1 MySQL thread id 23, OS thread handle 6131822592, query id 127 localhost 127.0.0.1 root update insert into course_member_statics(course_id,uid) values ('20230928145601000001',222222) on duplicate key update delete_flag=0 ------- TRX HAS BEEN WAITING 20 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 3 page no 5 n bits 72 index idx_courseid_uid of table `test`.`course_member_statics` trx id 1865 lock_mode X waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 20; hex 3230323330393238313435363031303030303031; asc 20230928145601000001;; 1: len 4; hex 0003640e; asc d ;; 2: len 8; hex 800000000000000b; asc ;; ------------------ TABLE LOCK table `test`.`course_member_statics` trx id 1865 lock mode IX RECORD LOCKS space id 3 page no 5 n bits 72 index idx_courseid_uid of table `test`.`course_member_statics` trx id 1865 lock_mode X waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 20; hex 3230323330393238313435363031303030303031; asc 20230928145601000001;; 1: len 4; hex 0003640e; asc d ;; 2: len 8; hex 800000000000000b; asc ;; ---TRANSACTION 1864, ACTIVE 58 sec 2 lock struct(s), heap size 1128, 1 row lock(s), undo log entries 1 MySQL thread id 22, OS thread handle 6129594368, query id 125 localhost 127.0.0.1 root TABLE LOCK table `test`.`course_member_statics` trx id 1864 lock mode IX RECORD LOCKS space id 3 page no 5 n bits 72 index idx_courseid_uid of table `test`.`course_member_statics` trx id 1864 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 20; hex 3230323330393238313435363031303030303031; asc 20230928145601000001;; 1: len 4; hex 0003640e; asc d ;; 2: len 8; hex 800000000000000b; asc ;;
看TRANSACTIONS段落,可以看到事務(wù)2本來(lái)是沒(méi)有持有記錄222222的X型記錄鎖的,在執(zhí)行這條語(yǔ)句后就有了,并且事務(wù)1自己對(duì)該記錄的X型next-key鎖置于等待中。這正是隱式鎖升級(jí)為顯示鎖的效果
3、最后執(zhí)行事務(wù)2的c語(yǔ)句,執(zhí)行SHOW ENGINE INNODB STATUS看日志
------------------------ LATEST DETECTED DEADLOCK ------------------------ 2023-10-06 15:35:02 0x16c617000 *** (1) TRANSACTION: TRANSACTION 1865, ACTIVE 36 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1128, 1 row lock(s), undo log entries 1 MySQL thread id 23, OS thread handle 6131822592, query id 127 localhost 127.0.0.1 root update insert into course_member_statics(course_id,uid) values ('20230928145601000001',222222) on duplicate key update delete_flag=0 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 3 page no 5 n bits 72 index idx_courseid_uid of table `test`.`course_member_statics` trx id 1865 lock_mode X waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 20; hex 3230323330393238313435363031303030303031; asc 20230928145601000001;; 1: len 4; hex 0003640e; asc d ;; 2: len 8; hex 800000000000000b; asc ;; *** (2) TRANSACTION: TRANSACTION 1864, ACTIVE 74 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 2 MySQL thread id 22, OS thread handle 6129594368, query id 129 localhost 127.0.0.1 root update insert ignore into course_member_statics(course_id,uid) values ('20230928145601000001',111111) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 3 page no 5 n bits 72 index idx_courseid_uid of table `test`.`course_member_statics` trx id 1864 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 20; hex 3230323330393238313435363031303030303031; asc 20230928145601000001;; 1: len 4; hex 0003640e; asc d ;; 2: len 8; hex 800000000000000b; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 3 page no 5 n bits 72 index idx_courseid_uid of table `test`.`course_member_statics` trx id 1864 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 20; hex 3230323330393238313435363031303030303031; asc 20230928145601000001;; 1: len 4; hex 0003640e; asc d ;; 2: len 8; hex 800000000000000b; asc ;; *** WE ROLL BACK TRANSACTION (1) ------------ TRANSACTIONS ------------ ---TRANSACTION 1864, ACTIVE 92 sec 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 2 MySQL thread id 22, OS thread handle 6129594368, query id 129 localhost 127.0.0.1 root TABLE LOCK table `test`.`course_member_statics` trx id 1864 lock mode IX RECORD LOCKS space id 3 page no 5 n bits 72 index idx_courseid_uid of table `test`.`course_member_statics` trx id 1864 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 20; hex 3230323330393238313435363031303030303031; asc 20230928145601000001;; 1: len 4; hex 0003640e; asc d ;; 2: len 8; hex 800000000000000b; asc ;; RECORD LOCKS space id 3 page no 5 n bits 72 index idx_courseid_uid of table `test`.`course_member_statics` trx id 1864 lock_mode X locks gap before rec insert intention Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 20; hex 3230323330393238313435363031303030303031; asc 20230928145601000001;; 1: len 4; hex 0003640e; asc d ;; 2: len 8; hex 800000000000000b; asc ;;
當(dāng)執(zhí)行這條語(yǔ)句后事務(wù)1的終端出現(xiàn)了死鎖的錯(cuò)誤提示:“Deadlock found when trying to get lock; try restarting transaction”
Deadlock found when trying to get lock; try restarting transaction
先看TRANSACTIONS段落,可以看出事務(wù)2分別對(duì)記錄222222持有X型記錄鎖和插入意向鎖,持有插入意向鎖是因?yàn)樵谟涗?22222插入插入111111時(shí)被間隙鎖阻塞了。
再看LATEST DETECTED DEADLOCK段落,可以看到事務(wù)1在等待事務(wù)2釋放記錄222222上的X型記錄鎖,同時(shí)事務(wù)2也在等待事務(wù)1在記錄222222上的間隙鎖釋放,出現(xiàn)了互相等待的現(xiàn)象,導(dǎo)致了死鎖發(fā)生。因?yàn)槭聞?wù)1只影響1條記錄,而事務(wù)2影響兩條記錄,所以將事務(wù)1回滾。
如果將執(zhí)行順序改成a,b,c也會(huì)出現(xiàn)死鎖,死鎖原因跟上面的類(lèi)似,至于其他組合:a,c,b、b,c,a、c,a,b、c,b,a都不會(huì)出現(xiàn)死鎖,至于原因大家可以自己分析一下。
上面所有的分析都是基于REPEATABLE READ隔離級(jí)別分析的,如果換成READ UNCOMMITTED,READ COMMITTED,SERIALIZABLE隔離級(jí)別還會(huì)出現(xiàn)死鎖嗎?
答案是會(huì)的,因?yàn)闊o(wú)論是哪種事務(wù)隔離級(jí)別,insert遇到唯一二級(jí)索引重復(fù)時(shí)都會(huì)給記錄添加next-key鎖(包含間隙鎖),且會(huì)觸發(fā)隱式鎖升級(jí)為顯示鎖,而這兩者正是導(dǎo)致出現(xiàn)死鎖的條件。
如何避免死鎖
既然存在死鎖的問(wèn)題,那么死鎖能避免嗎? 避免死鎖的方法:
改變事務(wù)執(zhí)行語(yǔ)句的順序
在確保業(yè)務(wù)功能正確的情況下,可以通過(guò)改變語(yǔ)句的執(zhí)行順序避免死鎖。當(dāng)然前提是得知道是什么原因?qū)е碌乃梨i
但很多時(shí)候語(yǔ)句的執(zhí)行順序會(huì)隨著數(shù)據(jù)的變化而變化的,無(wú)法人為控制,像上面死鎖的問(wèn)題insert ignore實(shí)際上有上千條數(shù)據(jù)批量插入,無(wú)法知道存儲(chǔ)引擎到底先執(zhí)行哪條后執(zhí)行哪條
給記錄添加合適的索引
建立合適的索引,縮小鎖作用的范圍和減少事務(wù)的執(zhí)行的時(shí)間,這樣能減少事務(wù)之間爭(zhēng)搶鎖的概率
雖然死鎖可以一定程度的減少,但無(wú)法完全避免,當(dāng)出現(xiàn)死鎖時(shí)也不必過(guò)于擔(dān)心,Mysql會(huì)以最小的代價(jià)回滾事務(wù),只要我們做了合理的重試機(jī)制(要注意重試的頻率,過(guò)快可能會(huì)導(dǎo)致進(jìn)一步死鎖),比如對(duì)異步的操作要做重試處理,因?yàn)榘l(fā)生錯(cuò)誤無(wú)法直接反饋給操作人,同步操作還好,發(fā)生死鎖會(huì)收到報(bào)錯(cuò)信息,重新執(zhí)行即可。
總結(jié)
Mysql insert 語(yǔ)句在特定的并發(fā)場(chǎng)景下也是會(huì)出現(xiàn)死鎖的,當(dāng)我們能分析出死鎖的原因,就能做到有的放矢。以下為本篇文章主要內(nèi)容
記錄鎖、間隙鎖、插入意向鎖、next-key鎖、隱式鎖的定義以及作用
隱式鎖在特定的條件下會(huì)升級(jí)為顯示鎖
insert語(yǔ)句在遇到唯一二級(jí)索引重復(fù)時(shí)會(huì)為記錄添加S型的next-key鎖,而insert... on duplicate key...則會(huì)添加X(jué)型的next-key鎖
只要?jiǎng)e的事務(wù)對(duì)記錄生成了一個(gè)顯式的間隙鎖的鎖結(jié)構(gòu),不論那個(gè)事務(wù)是已經(jīng)獲取到了該鎖,還是正在等待獲取,當(dāng)前事務(wù)要在該記錄前面插入新記錄都會(huì)被阻塞。
通過(guò)SHOW ENGINE INNODB STATUS查看、分析死鎖和加鎖過(guò)程
在能確定語(yǔ)句的執(zhí)行順序且保證業(yè)務(wù)功能正確的情況下可以通過(guò)改變語(yǔ)句的執(zhí)行順序避免死鎖。死鎖不能完全避免,要有合理的重試機(jī)制
到此這篇關(guān)于Mysql insert是否也會(huì)發(fā)生死鎖的文章就介紹到這了,更多相關(guān)Mysql insert死鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mysql installer community 8.0.12.0安裝圖文教程
這篇文章主要為大家詳細(xì)介紹了mysql installer community 8.0.12.0安裝圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08Linux系統(tǒng)下mysqlcheck修復(fù)數(shù)據(jù)庫(kù)命令(詳解)
下面小編就為大家?guī)?lái)一篇Linux系統(tǒng)下mysqlcheck修復(fù)數(shù)據(jù)庫(kù)命令(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,祝大家游戲愉快哦2016-12-12詳解Mysql數(shù)據(jù)庫(kù)date, datetime類(lèi)型設(shè)置0000-00-00默認(rèn)值(default)報(bào)錯(cuò)問(wèn)題
這篇文章主要介紹了詳解Mysql數(shù)據(jù)庫(kù)date, datetime類(lèi)型設(shè)置0000-00-00默認(rèn)值(default)報(bào)錯(cuò)問(wèn)題,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01mysql序號(hào)rownum行號(hào)實(shí)現(xiàn)方式
這篇文章主要介紹了mysql序號(hào)rownum行號(hào)實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12MySQL關(guān)閉密碼強(qiáng)度驗(yàn)證功能
本文通過(guò)實(shí)例代碼給大家介紹了mysql關(guān)閉密碼強(qiáng)度驗(yàn)證功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-06-06mysql逗號(hào)分隔的一行數(shù)據(jù)轉(zhuǎn)為多行數(shù)據(jù)的兩種方法
本文主要介紹了兩種將MySQL中逗號(hào)分隔的一行數(shù)據(jù)轉(zhuǎn)換為多行數(shù)據(jù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11MySQL5.6下windows msi安裝詳細(xì)介紹
這篇文章主要介紹了MySQL5.6下windows msi安裝詳細(xì)介紹,介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08