騰訊云:這個(gè)不可思議的死鎖你會(huì)解嗎?

來(lái)源: 騰訊云數(shù)據(jù)庫(kù)
作者:王起帆
時(shí)間:2021-04-29
17355
這篇文章將介紹一個(gè)“簡(jiǎn)單的死鎖”,這個(gè)死鎖產(chǎn)生的事物中SQL語(yǔ)句都只有一條,而且業(yè)務(wù)非常簡(jiǎn)單就是刪除一條記錄。兩個(gè)事物同時(shí)執(zhí)行以下兩個(gè)SQL語(yǔ)句就有可能死鎖。

我們都知道,數(shù)據(jù)庫(kù)系統(tǒng)中,不同線程并發(fā)訪問(wèn)數(shù)據(jù),為了保護(hù)數(shù)據(jù),在執(zhí)行SQL語(yǔ)句時(shí)候需要對(duì)數(shù)據(jù)加鎖。而死鎖是一個(gè)經(jīng)常遇到問(wèn)題,SQL語(yǔ)句加鎖和事物隔離級(jí)別,訪問(wèn)的索引是不是唯一,訪問(wèn)數(shù)據(jù)是否存在都有關(guān)系,往往死鎖分析非常復(fù)雜。這篇文章將介紹一個(gè)“簡(jiǎn)單的死鎖”,這個(gè)死鎖產(chǎn)生的事物中SQL語(yǔ)句都只有一條,而且業(yè)務(wù)非常簡(jiǎn)單就是刪除一條記錄。兩個(gè)事物同時(shí)執(zhí)行以下兩個(gè)SQL語(yǔ)句就有可能死鎖。

DELETE FROM dept_manager WHERE num = 0;

DELETE FROM dept_manager WHERE dept_no = 'd001';

一、死鎖模擬

死鎖模擬

首先介紹下表結(jié)構(gòu),這個(gè)表除了主鍵索引 PRIMARY,還有一個(gè)唯一索引 num 和一個(gè)非唯一索引 dept_no ,建表語(yǔ)句如下:

CREATE TABLE `dept_manager` (

  `emp_no` int(11) NOT NULL,

  `dept_no` char(4) NOT NULL,

  `num` int(11) NOT NULL,

  `to_date` date NOT NULL,

  PRIMARY KEY (`emp_no`),

  unique index(`num`),

  KEY `dept_no` (`dept_no`)

) ENGINE=InnoDB DEFAULT CHARSET=latin1;

然后再準(zhǔn)備下數(shù)據(jù):

INSERT INTO `dept_manager` VALUES (1001,'d001',0,'1991-10-01'),

                                  (1002,'d005',1,'9999-01-01'),

                                  (1005,'d002',3,'1989-12-17'),

                                  (1007,'d002',4,'9999-01-01'),

                                  (1008,'d004',7,'1988-09-09'),

                                  (1009,'d004',8,'1992-08-02'),

                                  (1010,'d005',9,'1996-08-30');

使用執(zhí)行兩個(gè)sql很難,使用 mysqlslap 來(lái)高并發(fā)碰碰運(yùn)氣:

# mysqlslap --create-schema dldb -q "begin;DELETE FROM dept_manager WHERE num = 0; rollback;" --number-of-queries=100000 -uroot -p123456 &

# mysqlslap --create-schema dldb -q "begin;DELETE FROM dept_manager WHERE dept_no = 'd001'; rollback;" --number-of-queries=100000 -uroot -p123456 &

這兩個(gè)事物非常都是刪除一行相同的數(shù)據(jù) (1001,'d001',0,'1991-10-01')只不過(guò)一個(gè)根據(jù)索引 num ,一個(gè)根據(jù)索引 dept_no 。

二、原因分析

1. 數(shù)據(jù)是怎么找到的?

要說(shuō)清楚死鎖產(chǎn)生原因,就要先理清楚這條SQL是怎么執(zhí)行的,會(huì)在那些地方加鎖。在此之前先說(shuō)說(shuō)數(shù)據(jù)庫(kù)是怎么找到我們要?jiǎng)h除的這行數(shù)據(jù)的。下面兩幅圖展示根據(jù)年齡為30來(lái)查記錄的示意圖。首先根據(jù) name 為 seven, 在 name 這個(gè)輔助索引查找,但是只能拿到主鍵的 id。隨后再根據(jù)主鍵id 去主鍵查找,這個(gè)過(guò)程稱為回表。訪問(wèn)數(shù)據(jù)是要通過(guò)索引的,而且數(shù)據(jù)就在主鍵索引上面,所以加鎖就是加在索引上面的。

640.webp.jpg

2. Delete 是怎么執(zhí)行的

Delete 刪除數(shù)據(jù)其實(shí)并不是把數(shù)據(jù)刪除了,只是把數(shù)據(jù)標(biāo)記一下,表示這里可以復(fù)用的,如果下次這里有數(shù)據(jù)要插入就可以直接復(fù)用原來(lái)空間里。所以Delete 和 Update 操作比較類似。Delete 和 Update 是根據(jù)條件找到第一條數(shù)據(jù),進(jìn)行修改,然后找到第二條數(shù)據(jù),以此類推直到再也查不到符合條件的數(shù)據(jù)。

3. 加鎖分析

我們以  DELETE FROM dept_manager WHERE num = 0; 為例,只有一個(gè)條件 num = 0, 因該是根據(jù) num = 0 在 num 索引中找到對(duì)應(yīng)的主鍵id, 隨后根據(jù)主鍵 id,找到對(duì)應(yīng)記錄,標(biāo)記成可復(fù)用狀態(tài)。除了刪除數(shù)據(jù)行記錄,對(duì)應(yīng)的索引也需要維護(hù)下,其他索引對(duì)應(yīng)位置也需要標(biāo)記成刪除狀態(tài)。這個(gè)表中主鍵索引 PRIMARY,唯一索引 num,非唯一索引 dept_no 的對(duì)應(yīng)位置都會(huì)加上鎖。同理第二個(gè)SQL語(yǔ)句執(zhí)行時(shí)候,加鎖位置也是一樣的。(可重復(fù)度隔離級(jí)別上,非唯一索引還要加上間隙鎖)。

既然加鎖上一樣的,那應(yīng)該是在不同索引加鎖順序是不一樣的。推測(cè)下對(duì)于 WHERE num = 0 應(yīng)該先在 num 上加鎖,隨后在主鍵加鎖,最后在 dept_no上,num ->PRIMARY-> dept_no。WHERE dept_no = 'd001';加鎖順序應(yīng)該是dept_no -> PRIMARY -> num。盡管這條SQL數(shù)據(jù)很簡(jiǎn)單,但是由于數(shù)據(jù)中索引比較多,加鎖順序也不一樣,導(dǎo)致了死鎖。

640.webp (1).jpg

三、場(chǎng)景驗(yàn)證

可以用 show engine innodb status ,來(lái)查看最近一次死鎖日志。事物1等待索引dept上的鎖 0: len 4; hex 64303031; asc d001;; 這里“64303031” 16進(jìn)制轉(zhuǎn)為字符為“d001” 與 WHERE dept_no = 'd001' 相對(duì)應(yīng)。事物2持有這個(gè)鎖的,事物1持有的鎖沒(méi)有顯示,應(yīng)該是主鍵上的鎖,這是符合預(yù)期的。

------------------------

LATEST DETECTED DEADLOCK

------------------------

2021-04-27 16:41:19 0x70000a6b1000

*** (1) TRANSACTION:

TRANSACTION 1681994, ACTIVE 0 sec updating or deleting

mysql tables in use 1, locked 1

LOCK WAIT 4 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 1

MySQL thread id 30, OS thread handle 123145456488448, query id 343687 localhost 127.0.0.1 root updating

DELETE FROM dept_manager WHERE num = 0

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:    #請(qǐng)求 dept_no上鎖

RECORD LOCKS space id 367 page no 5 n bits 80 index dept_no of table `employees`.`dept_manager` trx id 1681994 lock_mode X locks rec but not gap waiting

Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

 0: len 4; hex 64303031; asc d001;;

 1: len 4; hex 800003e9; asc     ;;


*** (2) TRANSACTION:

TRANSACTION 1681554, ACTIVE 0 sec starting index read

mysql tables in use 1, locked 1

3 lock struct(s), heap size 1136, 2 row lock(s)

MySQL thread id 106, OS thread handle 123145477099520, query id 341105 localhost 127.0.0.1 root updating

DELETE FROM dept_manager WHERE dept_no = 'd001'

*** (2) HOLDS THE LOCK(S):  # 持有 dept_no 上鎖

RECORD LOCKS space id 367 page no 5 n bits 80 index dept_no of table `employees`.`dept_manager` trx id 1681554 lock_mode X

Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

 0: len 4; hex 64303031; asc d001;;

 1: len 4; hex 800003e9; asc     ;;


*** (2) WAITING FOR THIS LOCK TO BE GRANTED: # 請(qǐng)求主鍵

RECORD LOCKS space id 367 page no 3 n bits 80 index PRIMARY of table `employees`.`dept_manager` trx id 1681554 lock_mode X locks rec but not gap waiting

Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 32

 0: len 4; hex 800003e9; asc     ;;

 1: len 6; hex 00000019aa4a; asc      J;;

 2: len 7; hex 2c000001b80ede; asc ,      ;;

 3: len 4; hex 64303031; asc d001;;

 4: len 4; hex 80000000; asc     ;;

 5: len 3; hex 8f8f41; asc   A;;


*** WE ROLL BACK TRANSACTION (2)

四、總結(jié)

本文介紹的樣例中,盡管SQL語(yǔ)句很簡(jiǎn)單,但由于表中有多個(gè)索引,對(duì)索引的訪問(wèn)順序不同,造成死鎖風(fēng)險(xiǎn)。為了避免數(shù)據(jù)庫(kù)中發(fā)生死鎖,建議:

1. 盡量開啟死鎖檢測(cè);

2. 盡量使用小事務(wù),在業(yè)務(wù)允許范圍內(nèi),將隔離級(jí)別改成讀已提交,可以減少不些不必要的鎖;

3. 避免全表掃描;

4. 避免較多索引;

5. 不同事務(wù)對(duì)表和行操作的順序盡量一致。

立即登錄,閱讀全文
版權(quán)說(shuō)明:
本文內(nèi)容來(lái)自于騰訊云數(shù)據(jù)庫(kù),本站不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。文章內(nèi)容系作者個(gè)人觀點(diǎn),不代表快出海對(duì)觀點(diǎn)贊同或支持。如有侵權(quán),請(qǐng)聯(lián)系管理員(zzx@kchuhai.com)刪除!
相關(guān)文章
騰訊云數(shù)據(jù)庫(kù)PostgreSQL全面支持PG 17
騰訊云數(shù)據(jù)庫(kù)PostgreSQL全面支持PG 17
即日起,騰訊云PostgreSQL全面支持PostgreSQL 17.0。所有用戶可使用大版本升級(jí)能力升級(jí)至最新的PostgreSQL 17.0進(jìn)行體驗(yàn),也可以在產(chǎn)品購(gòu)買頁(yè)直接購(gòu)買。
騰訊云
云服務(wù)
2024-12-152024-12-15
高可用這個(gè)問(wèn)題,加機(jī)器就能解決?
高可用這個(gè)問(wèn)題,加機(jī)器就能解決?
互聯(lián)網(wǎng)服務(wù)的可用性問(wèn)題是困擾企業(yè)IT人員的達(dá)摩克利斯之劍:防于未然,體現(xiàn)不出價(jià)值。已然發(fā)生,又面臨P0危機(jī)。就更別提穩(wěn)定性建設(shè)背后顯性的IT預(yù)算問(wèn)題與隱性的人員成本問(wèn)題。
騰訊云
云服務(wù)
2024-11-252024-11-25
TDSQL TDStore引擎版替換HBase:在歷史庫(kù)場(chǎng)景中的成本與性能優(yōu)勢(shì)
TDSQL TDStore引擎版替換HBase:在歷史庫(kù)場(chǎng)景中的成本與性能優(yōu)勢(shì)
HBase憑借其高可用性、高擴(kuò)展性和強(qiáng)一致性,以及在廉價(jià)PC服務(wù)器上的低部署成本,廣泛應(yīng)用于大規(guī)模數(shù)據(jù)分析。
騰訊云
云服務(wù)
2024-11-042024-11-04
復(fù)雜查詢性能弱,只讀分析引擎來(lái)幫忙
復(fù)雜查詢性能弱,只讀分析引擎來(lái)幫忙
隨著當(dāng)今業(yè)務(wù)的高速發(fā)展,復(fù)雜多表關(guān)聯(lián)的場(chǎng)景越來(lái)越普遍。但基于行式存儲(chǔ)的數(shù)據(jù)庫(kù)在進(jìn)行復(fù)雜查詢時(shí)性能相對(duì)較弱。
騰訊云
云服務(wù)
2024-11-022024-11-02
優(yōu)質(zhì)服務(wù)商推薦
更多
掃碼登錄
打開掃一掃, 關(guān)注公眾號(hào)后即可登錄/注冊(cè)
加載中
二維碼已失效 請(qǐng)重試
刷新
賬號(hào)登錄/注冊(cè)
個(gè)人VIP
小程序
快出海小程序
公眾號(hào)
快出海公眾號(hào)
商務(wù)合作
商務(wù)合作
投稿采訪
投稿采訪
出海管家
出海管家