最近在開發(fā)中使用到了多線程對(duì)同個(gè)表進(jìn)行讀寫操作楼镐,由于數(shù)據(jù)庫基礎(chǔ)渣渣馆截,寫完代碼后程序跑起來出現(xiàn)了死鎖抖锥。于是對(duì)日志進(jìn)行分析跟蹤宵喂,發(fā)現(xiàn)在執(zhí)行以下SQL時(shí)出現(xiàn)死鎖:
UPDATE linkgoo_message_queue SET state = 3 where state = 2;
很明顯在執(zhí)行上述SQL時(shí)糠赦,由于某個(gè)原因?qū)е卤韑inkgoo_message_queue被鎖或者表中相關(guān)記錄被鎖。那么問題來了锅棕,Mysql中Update的時(shí)候會(huì)怎么去加鎖呢拙泽?
網(wǎng)上查找資料并進(jìn)行試驗(yàn)后得到如下結(jié)論:
1.Mysql行鎖除了會(huì)鎖記錄之外,還有可能對(duì)索引加鎖裸燎,這取決于你的SQL寫法顾瞻;
2.UPDATE數(shù)據(jù)時(shí),若條件中包含主鍵德绿,那么只鎖該主鍵對(duì)應(yīng)的記錄荷荤;(注:網(wǎng)上找到的資料還提及:若操作了某個(gè)索引字段,比如 SET state=3 移稳,state為索引字段蕴纳,那么state這個(gè)索引也會(huì)被鎖上。但通過實(shí)驗(yàn)證明个粱,若兩條SQL條件中都存在主鍵古毛,且操作了相同的索引字段,是不會(huì)引發(fā)資源等待的都许!如果第一條SQL以主鍵為條件操作了索引字段稻薇,第二條SQL以索引字段為條件進(jìn)行UPDATE嫂冻,那么第二條SQL需要等待直到第一條SQL釋放鎖資源)
3.UPDATE數(shù)據(jù)時(shí),若條件中不包含主鍵但含有索引字段颖低,那么Mysql會(huì)先對(duì)索引加鎖絮吵,再對(duì)受影響的記錄加鎖。且如果SQL中還對(duì)索引字段進(jìn)行操作忱屑,那么還會(huì)對(duì)該索引字段追加鎖蹬敲。(注意加鎖是有順序的!正是因?yàn)檫@樣才導(dǎo)致的死鎖)
4.通過實(shí)驗(yàn)證明莺戒,對(duì)索引加鎖伴嗡,是對(duì)整個(gè)字段加鎖,而不是對(duì)索引的某個(gè)值(比如state=3)加鎖从铲。
通過上述的信息瘪校,我們?cè)賮矸治鲆幌逻@條SQL:
UPDATE linkgoo_message_queue SET state = 3 where state = 2;
首先Mysql會(huì)對(duì)state這個(gè)索引加鎖,然后再對(duì)state=2的所有記錄加鎖名段。由于存在SET state = 3阱扬,還會(huì)對(duì)state這個(gè)索引進(jìn)行加鎖,但之前已經(jīng)加過了伸辟,不會(huì)再重復(fù)加麻惶。
OK,我們?cè)倩仡櫼幌隆秵纹瑱C(jī)原理與嵌入式系統(tǒng)設(shè)計(jì)》里邊(別問我為什么是這本書,我會(huì)告訴你我是自動(dòng)化專業(yè)出身的萬金油嗎信夫?)對(duì)死鎖的定義:指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過程中窃蹋,由于競(jìng)爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象。
對(duì)于上邊的SQL静稻,由于其占用了state索引和相關(guān)的數(shù)據(jù)庫記錄警没,倘若要在并發(fā)條件下發(fā)生死鎖,那么若存在有線程先把記錄給鎖了振湾,再要鎖state索引杀迹,那么就有可能出現(xiàn)死鎖。
重新梳理代碼和業(yè)務(wù)邏輯押搪,在處理完message后树酪,會(huì)根據(jù)message.id對(duì)message的state進(jìn)行更新。問題到此已經(jīng)很明顯了嵌言,執(zhí)行UPDATE linkgoo_message_queue SET state = 3 where state = 2時(shí)嗅回,若state索引已經(jīng)鎖了,但還未對(duì)記錄進(jìn)行上鎖摧茴。此時(shí)根據(jù)message.id 去更新state,會(huì)對(duì)id=xxx這條記錄進(jìn)行上鎖绵载,并等待state索引的鎖釋放。而第一條SQL進(jìn)入記錄上鎖階段發(fā)現(xiàn)記錄已經(jīng)被鎖了,進(jìn)入了死循環(huán)發(fā)生死鎖娃豹。
那么上邊的問題怎么去解決呢焚虱?
在Mysql中UPDATE時(shí)不要直接將索引字段放到條件中進(jìn)行篩選,應(yīng)該采用SELECT先找出需要修改的數(shù)據(jù)懂版,再逐條對(duì)數(shù)據(jù)進(jìn)行UPDATE鹃栽。注意需要考慮如何讓SELECT出來的記錄在修改前還是查詢時(shí)的狀態(tài),而不是被其他線程UPDATE躯畴。