日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

一、背景

 

在預(yù)發(fā)環(huán)境中,由消息驅(qū)動(dòng)最終觸發(fā)執(zhí)行事務(wù)來(lái)寫(xiě)庫(kù)存,但是導(dǎo)致 MySQL 發(fā)生死鎖,寫(xiě)庫(kù)存失敗。

com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: rpc error: code = Aborted desc = Deadlock found when trying to get lock; try restarting transaction (errno 1213) (sqlstate 40001) (CallerID: ): Sql: "/* uag::omni_stock_rw;xx.xx.xx.xx:xxxxx;xx.xx.xx.xx:xxxxx;xx.xx.xx.xx:xxxxx;enable */ insert into stock_info(tenant_id, sku_id, store_id, avAIlable_num, actual_good_num, order_num, created, modified, SAVE_VERSION, stock_id) values (:vtg1, :vtg2, :_store_id0, :vtg4, :vtg5, :vtg6, now(), now(), :vtg7, :__seq0) /* vtgate:: keyspace_id:e267ed155be60efe */", BindVars: {__seq0: "type:INT64 value:"29332459" "_store_id0: "type:INT64 value:"50650235" "vtg1: "type:INT64 value:"71" "vtg2: "type:INT64 value:"113817631" "vtg3: "type:INT64 value:"50650235" "vtg4: "type:FLOAT64 value:"1000.000" "vtg5: "type:FLOAT64 value:"1000.000" "vtg6: "type:INT64 value:"0" "vtg7: "type:INT64 value:"20937611645" "}

初步排查,在同一時(shí)刻有兩條請(qǐng)求進(jìn)行寫(xiě)庫(kù)存的操作。

MySQL 事務(wù)死鎖問(wèn)題排查

??時(shí)間前后相差 1s,但最終執(zhí)行結(jié)果是,這兩個(gè)事務(wù)相互死鎖,均失敗。

事務(wù)定義非常簡(jiǎn)單,偽代碼描述如下:

start transaction

// 1、查詢數(shù)據(jù)

data = select for update(tenantId, storeId, skuId);

if (data == null) {

// 插入數(shù)據(jù)

insert(tenantId, storeId, skuId);

} else {

// 更新數(shù)據(jù)

update(tenantId, storeId, skuId);

}

end transaction

該數(shù)據(jù)庫(kù)表的索引結(jié)構(gòu)如下:

索引類型索引組成列PRIMARY KEY(`stock_id`)UNIQUE KEY(`sku_id`,`store_id`)

所使用的數(shù)據(jù)庫(kù)引擎為 Innodb,隔離級(jí)別為 RR [Repeatable Read] 可重復(fù)讀。?

二、分析思路

首先了解下 Innodb 引擎中有關(guān)于鎖的內(nèi)容

2.1 Innodb 中的鎖

2.1.1 行級(jí)鎖

在 Innodb 引擎中,行級(jí)鎖的實(shí)現(xiàn)方式有以下三種:

名稱描述Record Lock鎖定單行記錄,在隔離級(jí)別 RC 和 RR 下均支持。Gap Lock間隙鎖,鎖定索引記錄間隙(不包含查詢的記錄),鎖定區(qū)間為左開(kāi)右開(kāi),僅在 RR 隔離級(jí)別下支持。Next-Key Lock臨鍵鎖,鎖定查詢記錄所在行,同時(shí)鎖定前面的區(qū)間,故區(qū)間為左開(kāi)右閉,僅在 RR 隔離級(jí)別下支持。

同時(shí),在 Innodb 中實(shí)現(xiàn)了標(biāo)準(zhǔn)的行鎖,按照鎖定類型又可分為兩類:

名稱符號(hào)描述共享鎖S允許事務(wù)讀一行數(shù)據(jù),阻止其他事務(wù)獲得相同的數(shù)據(jù)集的排他鎖。排他鎖X允許事務(wù)刪除或更新一行數(shù)據(jù),阻止其他事務(wù)獲得相同數(shù)據(jù)集的共享鎖和排他鎖。

簡(jiǎn)言之,當(dāng)某個(gè)事物獲取了共享鎖后,其他事物只能獲取共享鎖,若想獲取排他鎖,必須要等待共享鎖釋放;若某個(gè)事物獲取了排他鎖,則其余事物無(wú)論獲取共享鎖還是排他鎖,都需要等待排他鎖釋放。如下表所示:

將獲取的鎖(下) 已獲取的鎖(右)共享鎖 S排他鎖 X共享鎖 S兼容不兼容排他鎖 X不兼容不兼容

2.1.2 RR 隔離級(jí)別下加鎖示例

假如現(xiàn)在有這樣一張表 user,下面將針對(duì)不同的查詢請(qǐng)求逐一分析加鎖情況。user 表定義如下:

CREATE TABLE `user` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',

`user_id` bigint(20) DEFAULT NULL COMMENT '用戶id',

`mobile_num` bigint(20) NOT NULL COMMENT '手機(jī)號(hào)',

PRIMARY KEY (`id`),

UNIQUE KEY `IDX_USER_ID` (`user_id`),

KEY `IDX_MOBILE_NUM` (`mobile_num`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶信息表'

其中主鍵 id 與 user_id 為唯一索引,user_name 為普通索引。

假設(shè)該表中現(xiàn)有數(shù)據(jù)如下所示:

iduser_idmobile_num113556887999

下面將使用 select ... for update 語(yǔ)句進(jìn)行查詢,分別針對(duì)唯一索引、普通索引來(lái)進(jìn)行舉例。

1、唯一索引等值查詢

select * from user

where id = 5 for update

select * from user

where user_id = 5 for update

在這兩條 SQL 中,Innodb 執(zhí)行查詢過(guò)程時(shí),會(huì)如何加鎖呢?

?我們都知道 Innodb 默認(rèn)的索引數(shù)據(jù)結(jié)構(gòu)為 B + 樹(shù),B + 樹(shù)的葉子結(jié)點(diǎn)包含指向下一個(gè)葉子結(jié)點(diǎn)的指針。在查詢過(guò)程中,會(huì)按照 B + 樹(shù)的搜索方式來(lái)進(jìn)行查找,其底層原理類似二分查找。故在加鎖過(guò)程中會(huì)按照以下兩條原則進(jìn)行加鎖:

1. 只會(huì)對(duì)滿足查詢目標(biāo)附近的區(qū)間加鎖,并不是對(duì)搜索路徑中的所有區(qū)間都加鎖。本例中對(duì)搜索 id=5 或者 user_id=5 時(shí),最終可以定位到滿足該搜索條件的區(qū)域 (1,5]。

2. 加鎖時(shí),會(huì)以 Next key Lock 為加鎖單位。那按照 1 滿足的區(qū)域進(jìn)行加 Next key Lock 鎖(左開(kāi)右閉),同時(shí)因?yàn)?id=5 或者 user_id=5 存在,所以該 Next key Lock 會(huì)退化為 Record Lock,故只對(duì) id=5 或 user_id=5 這個(gè)索引行加鎖。

如果查詢的 id 不存在,例如:

select * from user

where id = 6 for update

按照上面兩條原則,首先按照滿足查詢目標(biāo)條件附近區(qū)域加鎖,所以最終會(huì)找到的區(qū)間為 (5,8]。因?yàn)?id=6 這條記錄并不存在,所以 Next key Lock (5, 8] 最終會(huì)退化為 Gap Lock,即對(duì)索引 (5,8) 加間隙鎖。

2、唯一索引范圍查詢

select * from user

where id >= 4 and id <8 for update

同理,在范圍查詢中,會(huì)首先匹配左值 id=4,此時(shí)會(huì)對(duì)區(qū)間 (1,5] 加 Next key Lock,因?yàn)?id=4 不存在,所以鎖退化為 Gap Lock (1,5);接著會(huì)往后繼續(xù)查找 id=8 的記錄,直到找到第一個(gè)不滿足的區(qū)間,即 Next key Lock (8, 9],因?yàn)?8 不在范圍內(nèi),所以鎖退化為 Gap Lock (8, 9)。故該范圍查詢最終會(huì)鎖的區(qū)域?yàn)?(1, 9)

3、非唯一索引等值查詢

對(duì)非唯一索引查詢時(shí),與上述的加鎖方式稍有區(qū)別。除了要對(duì)包含查詢值區(qū)間內(nèi)加 Next key Lock 之外,還要對(duì)不滿足查詢條件的下一個(gè)區(qū)間加 Gap Lock,也就是需要加兩把鎖。

select * from user

where mobile_num = 6 for update

需要對(duì)索引 (3, 6] 加 Next key Lock,因?yàn)榇藭r(shí)是非唯一索引,那么也就有可能有多個(gè) 6 存在,所以此時(shí)不會(huì)退化為 Record Lock;此外還要對(duì)不滿足該查詢條件的下一個(gè)區(qū)間加 Gap Lock,也就是對(duì)索引 (6,7) 加鎖。故總體來(lái)看,對(duì)索引加了 (3,6] Next key Lock 和 (6, 7) Gap Lock。

若非唯一索引不命中時(shí),如下:

select * from user

where mobile_num = 8 for update

那么需要對(duì)索引 (7, 9] 加 Next key Lock,又因?yàn)?8 不存在,所以鎖退化為 Gap Lock (7, 9)

4、非唯一索引范圍查詢

select * from user

where mobile_num >= 6 and mobile_num < 8

for update

首先先匹配 mobile_num=6,此時(shí)會(huì)對(duì)索引 (3, 6] 加 Next Key Lock,雖然此時(shí)非唯一索引存在,但是不會(huì)退化為 Record Lock;其次再看后半部分的查詢 mobile_num=8,需要對(duì)索引 (7, 9] 加 Next key Lock,又因?yàn)?8 不存在,所以退化為 Gap Lock (7, 9)。最終,需要對(duì)索引行加 Next key Lock (3, 6] 和 Gap Lock (7, 9)。

2.1.3 意向鎖(Intention Locks)

Innodb 為了支持多粒度鎖定,引入了意向鎖。意向鎖是一種表級(jí)鎖,用于表明事務(wù)將要對(duì)某張表某行數(shù)據(jù)操作而進(jìn)行的鎖定。同樣,意向鎖也分為類:共享意向鎖(IS)和排他意向鎖(IX)。

名稱符號(hào)描述共享意向鎖IS表明事務(wù)將要對(duì)表的個(gè)別行設(shè)置共享鎖排他意向鎖IX表明事務(wù)將要對(duì)表的個(gè)別行設(shè)置排他鎖

例如 select ... lock in shared mode 會(huì)設(shè)置共享意向鎖 IS;select ... for update 會(huì)設(shè)置排他意向鎖 IX

設(shè)置意向鎖時(shí)需要按照以下兩條原則進(jìn)行設(shè)置:

1. 當(dāng)事務(wù)需要申請(qǐng)共享鎖 S 時(shí),必須先對(duì)申請(qǐng)共享意向 IS 鎖或更強(qiáng)的鎖

2. 當(dāng)事務(wù)需要申請(qǐng)排他鎖 X 時(shí),必須先對(duì)申請(qǐng)排他意向 IX 鎖

?表級(jí)鎖兼容性矩陣如下表:

將獲取的鎖(下)/ 已獲取的鎖(右)XIXSISX沖突沖突沖突沖突IX沖突兼容沖突兼容S沖突沖突兼容兼容IS沖突兼容兼容兼容

如果請(qǐng)求鎖的事務(wù)與現(xiàn)有鎖兼容,則會(huì)將鎖授予該事務(wù),但如果與現(xiàn)有鎖沖突,則不會(huì)授予該事務(wù)。事務(wù)等待,直到?jīng)_突的現(xiàn)有鎖被釋放。

意向鎖的目的就是為了說(shuō)明事務(wù)正在對(duì)表的一行進(jìn)行鎖定,或?qū)⒁獙?duì)表的一行進(jìn)行鎖定。在意向鎖概念中,除了對(duì)全表加鎖會(huì)導(dǎo)致意向鎖阻塞外,其余情況意向鎖均不會(huì)阻塞任何請(qǐng)求!

2.1.4 插入意向鎖

插入意向鎖是一種特殊的意向鎖,同時(shí)也是一種特殊的 “Gap Lock”,是在 Insert 操作之前設(shè)置的 Gap Lock。

如果此時(shí)有多個(gè)事務(wù)執(zhí)行 insert 操作,恰好需要插入的位置都在同一個(gè) Gap Lock 中,但是并不是在 Gap Lock 的同一個(gè)位置時(shí),此時(shí)的插入意向鎖彼此之間不會(huì)阻塞。

2.2 過(guò)程分析

回到本文的問(wèn)題上來(lái),本文中有兩個(gè)事務(wù)執(zhí)行同樣的動(dòng)作,分別為先執(zhí)行 select ... for update 獲取排他鎖,其次判斷若為空,則執(zhí)行 insert 動(dòng)作,否則執(zhí)行 update 動(dòng)作。偽代碼描述如下:

start transaction

// 1、查詢數(shù)據(jù)

data = select for update(tenantId, storeId, skuId);

if (data == null) {

// 插入數(shù)據(jù)

insert(tenantId, storeId, skuId);

} else {

// 更新數(shù)據(jù)

update(tenantId, storeId, skuId);

}

end transaction

?現(xiàn)在對(duì)這兩個(gè)事務(wù)所執(zhí)行的動(dòng)作進(jìn)行逐一分析,如下表所示:

時(shí)間點(diǎn)事務(wù) A事務(wù) B潛在動(dòng)作1開(kāi)始事務(wù)開(kāi)始事務(wù)?2執(zhí)行 select ... for update 操作?事務(wù) A 申請(qǐng)到 IX 事務(wù) A 申請(qǐng)到 X,Gap Lock3?執(zhí)行 select ... for update 操作事務(wù) B 申請(qǐng)到 IX,與事務(wù) A 的 IX 不沖突。 事務(wù) B 申請(qǐng)到 Gap Lock,Gap Lock 可共存。4執(zhí)行 insert 操作?事務(wù) A 先申請(qǐng)插入意向鎖 IX,與事務(wù) B 的 Gap Lock 沖突,等待事務(wù) B 的 Gap Lock 釋放。5?執(zhí)行 insert 操作事務(wù) B 先申請(qǐng)插入意向鎖 IX,與事務(wù) A 的 Gap Lock 沖突,等待事務(wù) A 的 Gap Lock 釋放。6??死鎖檢測(cè)器檢測(cè)到死鎖

詳細(xì)分析:

  • 時(shí)間點(diǎn) 1,事務(wù) A 與事務(wù) B 開(kāi)始執(zhí)行事務(wù)
  • 時(shí)間點(diǎn) 2,事務(wù) A 執(zhí)行 select ... for update 操作,執(zhí)行該操作時(shí)首先需要申請(qǐng)意向排他鎖 IX 作用于表上,接著申請(qǐng)到了排他鎖 X 作用于區(qū)間,因?yàn)椴樵兊闹挡淮嬖冢?Next key Lock 退化為 Gap Lock。
  • 時(shí)間點(diǎn) 3,事務(wù) B 執(zhí)行 select ... for update 操作,首先申請(qǐng)意向排他鎖 IX,根據(jù) 2.1.3 節(jié)表級(jí)鎖兼容矩陣可以看到,意向鎖之間是相互兼容的,故申請(qǐng) IX 成功。由于查詢值不存在,故可以申請(qǐng) X 的 Gap Lock,而 Gap Lock 之間是可以共存的,不論是共享還是排他。這一點(diǎn)可以參考 Innodb 關(guān)于 Gap Lock 的描述,關(guān)鍵描述本文粘貼至此:

Gap locks can co-exist. A gap lock taken by one transaction does not prevent another transaction from taking a gap lock on the same gap. There is no difference between shared and exclusive gap locks. They do not conflict with each other, and they perform the same function.

  • 時(shí)間點(diǎn) 4,事務(wù) A 執(zhí)行 insert 操作前,首先會(huì)申請(qǐng)插入意向鎖,但此時(shí)事務(wù) B 已經(jīng)擁有了插入?yún)^(qū)間的排他鎖,根據(jù) 2.1.3 節(jié)表級(jí)鎖兼容矩陣可知,在已有 X 鎖情況下,再次申請(qǐng) IX 鎖是沖突的,需要等待事務(wù) B 對(duì) X Gap Lock 釋放。
  • 時(shí)間點(diǎn) 5,事務(wù) B 執(zhí)行 insert 操作前,也會(huì)首先申請(qǐng)插入意向鎖,此時(shí)事務(wù) A 也對(duì)插入?yún)^(qū)間擁有 X Gap Lock,因此需要等待事務(wù) A 對(duì) X 鎖進(jìn)行釋放。
  • 時(shí)間點(diǎn) 6,事務(wù) A 與事務(wù) B 均在等待對(duì)方釋放 X 鎖,后被 MySQL 的死鎖檢測(cè)器檢測(cè)到后,報(bào) Dead Lock 錯(cuò)誤。

?思考:假如 select ... for update 查詢的數(shù)據(jù)存在時(shí),會(huì)是什么樣的過(guò)程呢?過(guò)程如下表:

時(shí)間點(diǎn)事務(wù) A事務(wù) B潛在動(dòng)作1開(kāi)始事務(wù)開(kāi)始事務(wù)?2執(zhí)行 select ... for update 操作?事務(wù) A 申請(qǐng)到 IX 事務(wù) A 申請(qǐng)到 X 行鎖,因數(shù)據(jù)存在故鎖退化為 Record Lock。3?執(zhí)行 select ... for update 操作事務(wù) B 申請(qǐng)到 IX,與事務(wù) A 的 IX 不沖突。 事務(wù) B 想申請(qǐng)目標(biāo)行的 Record Lock,此時(shí)需要等待事務(wù) A 釋放該鎖資源。4執(zhí)行 update 操作?事務(wù) A 先申請(qǐng)插入意向鎖 IX,此時(shí)事務(wù) B 僅僅擁有 IX 鎖資源,兼容,不沖突。然后事務(wù) A 擁有 X 的 Record Lock,故執(zhí)行更新。5commit?事務(wù) A 提交,釋放 IX 與 X 鎖資源。6?執(zhí)行 select ... for update 操作事務(wù) B 事務(wù) B 此時(shí)獲取到 X Record Lock。7?執(zhí)行 update 操作事務(wù) B 擁有 X Record Lock 執(zhí)行更新8?commit事務(wù) B 釋放 IX 與 X 鎖資源

也就是當(dāng)查詢數(shù)據(jù)存在時(shí),不會(huì)出現(xiàn)死鎖問(wèn)題。?

三、解決方法

1、在事務(wù)開(kāi)始之前,采用 CAS + 分布式鎖來(lái)控制并發(fā)寫(xiě)請(qǐng)求。分布式鎖 key 可以設(shè)置為 store_skuId_version

2、事務(wù)過(guò)程可以改寫(xiě)為:

start transaction

// RR級(jí)別下,讀視圖

data = select from table(tenantId, storeId, skuId)

if (data == null) {

// 可能出現(xiàn)寫(xiě)并發(fā)

insert

} else {

data = select for update(tenantId, storeId, skuId)

update

}

end transaction

雖然解決了插入數(shù)據(jù)不存在時(shí)會(huì)出現(xiàn)的死鎖問(wèn)題,但是可能存在并發(fā)寫(xiě)的問(wèn)題,第一個(gè)事務(wù)獲得鎖會(huì)首先插入成功,第二個(gè)事務(wù)等待第一個(gè)事務(wù)提交后,插入數(shù)據(jù),因?yàn)閿?shù)據(jù)存在了所以報(bào)錯(cuò)回滾。

3、調(diào)整事務(wù)隔離級(jí)別為 RC,在 RC 下沒(méi)有 next key lock(注意,此處并不準(zhǔn)確,RC 會(huì)有少部分情況加 Next key lock),故此時(shí)僅僅會(huì)有 record lock,所以事務(wù) 2 進(jìn)行 select for update 時(shí)需要等待事務(wù) 1 提交。

參考文獻(xiàn)

[1] Innodb 鎖官方文檔:https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html?

[2] https://blog.csdn.NET/qq_43684538/article/details/131450395?

[3] https://www.jianshu.com/p/027afd6345d5?

[4] https://www.cnblogs.com/micrari/p/8029710.html?

若有錯(cuò)誤,還望批評(píng)指正

 

作者:京東零售 劉哲
來(lái)源:京東云開(kāi)發(fā)者社區(qū) 轉(zhuǎn)載請(qǐng)注明來(lái)源

分享到:
標(biāo)簽:MySQL
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定