MySQL 優(yōu)化 index merge(索引合并)引起的死鎖分析(強(qiáng)烈推薦)
閱讀本文大概需要 5 分鐘。
來自:blog.csdn.net/daidaineteasy/article/details/109266083
背景
UPDATE?test_table?SET?`status`?=?1?WHERE?`trans_id`?=?'xxx1'?AND?`status`?=?0;
UPDATE?test_table?SET?`status`?=?1?WHERE?`trans_id`?=?'xxx2'?AND?`status`?=?0;
死鎖日志
***?(1)?TRANSACTION:
TRANSACTION?791913819,?ACTIVE?0?sec?starting?index?read,?thread?declared?inside?InnoDB?4999
mysql?tables?in?use?3,?locked?3
LOCK?WAIT?4?lock?struct(s),?heap?size?1184,?3?row?lock(s)
MySQL?thread?id?462005230,?OS?thread?handle?0x7f55d5da3700,?query?id?2621313306?x.x.x.x?test_user?Searching?rows?for?update
UPDATE?test_table?SET?`status`?=?1?WHERE?`trans_id`?=?'xxx1'?AND?`status`?=?0;
***?(1)?WAITING?FOR?THIS?LOCK?TO?BE?GRANTED:
RECORD?LOCKS?space?id?110?page?no?39167?n?bits?1056?index?`idx_status`?of?table?`test`.`test_table`?trx?id?791913819?lock_mode?X?waiting
Record?lock,?heap?no?495?PHYSICAL?RECORD:?n_fields?2;?compact?format;?info?bits?0
***?(2)?TRANSACTION:
TRANSACTION?791913818,?ACTIVE?0?sec?starting?index?read,?thread?declared?inside?InnoDB?4999
mysql?tables?in?use?3,?locked?3
5?lock?struct(s),?heap?size?1184,?4?row?lock(s)
MySQL?thread?id?462005231,?OS?thread?handle?0x7f55cee63700,?query?id?2621313305?x.x.x.x?test_user?Searching?rows?for?update
UPDATE?test_table?SET?`status`?=?1?WHERE?`trans_id`?=?'xxx2'?AND?`status`?=?0;
***?(2)?HOLDS?THE?LOCK(S):
RECORD?LOCKS?space?id?110?page?no?39167?n?bits?1056?index?`idx_status`?of?table?`test`.`test_table`?trx?id?791913818?lock_mode?X
Record?lock,?heap?no?495?PHYSICAL?RECORD:?n_fields?2;?compact?format;?info?bits?0
***?(2)?WAITING?FOR?THIS?LOCK?TO?BE?GRANTED:
RECORD?LOCKS?space?id?110?page?no?41569?n?bits?88?index?`PRIMARY`?of?table?`test`.`test_table`?trx?id?791913818?lock_mode?X?locks?rec?but?not?gap?waiting
Record?lock,?heap?no?14?PHYSICAL?RECORD:?n_fields?30;?compact?format;?info?bits?0
***?WE?ROLL?BACK?TRANSACTION?(1)
表結(jié)構(gòu)
CREATE?TABLE?`test_table`?(
??`id`?int(11)?NOT?NULL?AUTO_INCREMENT,
??`trans_id`?varchar(21)?NOT?NULL,
??`status`?int(11)?NOT?NULL,
??PRIMARY?KEY?(`id`),
??UNIQUE?KEY?`uniq_trans_id`?(`trans_id`)?USING?BTREE,
??KEY?`idx_status`?(`status`)?USING?BTREE
)?ENGINE=InnoDB?AUTO_INCREMENT=1?DEFAULT?CHARSET=utf8
聚簇索引:?將數(shù)據(jù)存儲與索引放到了一塊,索引結(jié)構(gòu)的葉子節(jié)點(diǎn)保存了行數(shù)據(jù)。 輔助索引:?輔助索引葉子節(jié)點(diǎn)存儲的是主鍵值,也就是聚簇索引的鍵值。
執(zhí)行計(jì)劃

uniq_trans_id?索引,又用到了?idx_status?索引,Using intersect(uniq_trans_id,idx_status)的意思是通過兩個(gè)索引獲取交集。為什么會用 index_merge(索引合并)
index merge?優(yōu)化技術(shù),對同一個(gè)表可以使用多個(gè)索引分別進(jìn)行條件掃描。UPDATE?test_table?SET?`status`?=?1?WHERE?`trans_id`?=?'38'?AND?`status`?=?0?;
trans_id = ‘38’這個(gè)條件,利用?uniq_trans_id?索引找到葉子節(jié)點(diǎn)中保存的id值;同時(shí)會根據(jù)?status = 0這個(gè)條件,利用?idx_status?索引找到葉子節(jié)點(diǎn)中保存的id值;然后將找到的兩組id值取交集,最終通過交集后的id回表,也就是通過 PRIMARY 索引找到葉子節(jié)點(diǎn)中保存的行數(shù)據(jù)。uniq_trans_id?已經(jīng)是一個(gè)唯一索引了,通過這個(gè)索引最終只能找到最多一條數(shù)據(jù),那MySQL優(yōu)化器為啥還要用兩個(gè)索引取交集,再回表進(jìn)行查詢呢,這樣不是多了一次?idx_status?索引查找的過程么。我們來分析一下這兩種情況執(zhí)行過程。根據(jù)? trans_id = ‘38’查詢條件,利用uniq_trans_id?索引找到葉子節(jié)點(diǎn)中保存的id值;通過找到的id值,利用PRIMARY索引找到葉子節(jié)點(diǎn)中保存的行數(shù)據(jù); 再通過? status = 0?條件對找到的行數(shù)據(jù)進(jìn)行過濾。
Using intersect(uniq_trans_id,idx_status):根據(jù)? trans_id = ‘38’?查詢條件,利用?uniq_trans_id?索引找到葉子節(jié)點(diǎn)中保存的id值;根據(jù)? status = 0?查詢條件,利用?idx_status?索引找到葉子節(jié)點(diǎn)中保存的id值;將1/2中找到的id值取交集,然后利用PRIMARY索引找到葉子節(jié)點(diǎn)中保存的行數(shù)據(jù)
status = 0?的數(shù)據(jù)非常少,這也是優(yōu)化器考慮用第二種情況的原因之一)。index_merge?就死鎖了
idx_status?索引和 PRIMARY (聚簇索引) 上都存在重合交叉的部分,這樣就為死鎖造成了條件。
解決方案
一、從代碼層面
where 查詢條件中,只傳? trans_id?,將數(shù)據(jù)查詢出來后,在代碼層面判斷 status 狀態(tài)是否為0;使用? force index(uniq_trans_id)?強(qiáng)制查詢語句使用?uniq_trans_id?索引;where 查詢條件后邊直接用 id 字段,通過主鍵去更新。
二、從MySQL層面
刪除? idx_status?索引或者建一個(gè)包含這倆列的聯(lián)合索引;將MySQL優(yōu)化器的 index merge優(yōu)化關(guān)閉。
內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬并發(fā)、消息隊(duì)列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper、數(shù)據(jù)結(jié)構(gòu)、限流熔斷降級......等技術(shù)棧!
?戳閱讀原文領(lǐng)取!? ? ? ? ? ? ? ??? ??? ? ? ? ? ? ? ? ? ?朕已閱?
評論
圖片
表情

