Mysql行鎖機(jī)制引發(fā)的血案

最近在開發(fā)中使用到了多線程對同個表進(jìn)行讀寫操作,由于數(shù)據(jù)庫基礎(chǔ)渣渣,寫完代碼后程序跑起來出現(xiàn)了死鎖。于是對日志進(jìn)行分析跟蹤,發(fā)現(xiàn)在執(zhí)行以下SQL時出現(xiàn)死鎖:

UPDATE linkgoo_message_queue SET state = 3 where state = 2;

很明顯在執(zhí)行上述SQL時,由于某個原因?qū)е卤韑inkgoo_message_queue被鎖或者表中相關(guān)記錄被鎖。那么問題來了,Mysql中Update的時候會怎么去加鎖呢?

網(wǎng)上查找資料并進(jìn)行試驗(yàn)后得到如下結(jié)論:

1.Mysql行鎖除了會鎖記錄之外,還有可能對索引加鎖,這取決于你的SQL寫法;

2.UPDATE數(shù)據(jù)時,若條件中包含主鍵,那么只鎖該主鍵對應(yīng)的記錄;(注:網(wǎng)上找到的資料還提及:若操作了某個索引字段,比如 SET state=3 ,state為索引字段,那么state這個索引也會被鎖上。但通過實(shí)驗(yàn)證明,若兩條SQL條件中都存在主鍵,且操作了相同的索引字段,是不會引發(fā)資源等待的!如果第一條SQL以主鍵為條件操作了索引字段,第二條SQL以索引字段為條件進(jìn)行UPDATE,那么第二條SQL需要等待直到第一條SQL釋放鎖資源

3.UPDATE數(shù)據(jù)時,若條件中不包含主鍵但含有索引字段,那么Mysql會先對索引加鎖,再對受影響的記錄加鎖。且如果SQL中還對索引字段進(jìn)行操作,那么還會對該索引字段追加鎖。(注意加鎖是有順序的!正是因?yàn)檫@樣才導(dǎo)致的死鎖

4.通過實(shí)驗(yàn)證明,對索引加鎖,是對整個字段加鎖,而不是對索引的某個值(比如state=3)加鎖。

通過上述的信息,我們再來分析一下這條SQL:

UPDATE linkgoo_message_queue SET state = 3 where state = 2;

首先Mysql會對state這個索引加鎖,然后再對state=2的所有記錄加鎖。由于存在SET state = 3,還會對state這個索引進(jìn)行加鎖,但之前已經(jīng)加過了,不會再重復(fù)加。

OK,我們再回顧一下《單片機(jī)原理與嵌入式系統(tǒng)設(shè)計(jì)》里邊(別問我為什么是這本書,我會告訴你我是自動化專業(yè)出身的萬金油嗎?)對死鎖的定義:指兩個或兩個以上的進(jìn)程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象。

對于上邊的SQL,由于其占用了state索引和相關(guān)的數(shù)據(jù)庫記錄,倘若要在并發(fā)條件下發(fā)生死鎖,那么若存在有線程先把記錄給鎖了,再要鎖state索引,那么就有可能出現(xiàn)死鎖。

重新梳理代碼和業(yè)務(wù)邏輯,在處理完message后,會根據(jù)message.id對message的state進(jìn)行更新。問題到此已經(jīng)很明顯了,執(zhí)行UPDATE linkgoo_message_queue SET state = 3 where state = 2時,若state索引已經(jīng)鎖了,但還未對記錄進(jìn)行上鎖。此時根據(jù)message.id 去更新state,會對id=xxx這條記錄進(jìn)行上鎖,并等待state索引的鎖釋放。而第一條SQL進(jìn)入記錄上鎖階段發(fā)現(xiàn)記錄已經(jīng)被鎖了,進(jìn)入了死循環(huán)發(fā)生死鎖。

那么上邊的問題怎么去解決呢?

在Mysql中UPDATE時不要直接將索引字段放到條件中進(jìn)行篩選,應(yīng)該采用SELECT先找出需要修改的數(shù)據(jù),再逐條對數(shù)據(jù)進(jìn)行UPDATE。注意需要考慮如何讓SELECT出來的記錄在修改前還是查詢時的狀態(tài),而不是被其他線程UPDATE。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容