MySQL 死鎖問題分析
線上某服務時不時報出如下異常(大約一天二十多次):“Deadlock found when trying to get lock;”。
Oh, My God! 是死鎖問題。儘管報錯不多,對效能目前看來也無太大影響,但還是需要解決,保不齊哪天成為效能瓶頸。
為了更系統的分析問題,本文將從死鎖檢測、索引隔離級別與鎖的關係、死鎖成因、問題定位這五個方面來展開討論。
圖1 應用日誌
1 死鎖是怎麼被發現的?
1.1 死鎖成因&&檢測方法
左圖那兩輛車造成死鎖了嗎?不是!右圖四輛車造成死鎖了嗎?是!
圖2 死鎖描述
我們mysql用的儲存引擎是innodb,從日誌來看,innodb主動探知到死鎖,並回滾了某一苦苦等待的事務。問題來了,innodb是怎麼探知死鎖的?直觀方法是在兩個事務相互等待時,當一個等待時間超過設定的某一閥值時,對其中一個事務進行回滾,另一個事務就能繼續執行。這種方法簡單有效,在innodb中,引數innodb_lock_wait_timeout用來設定超時時間。
僅用上述方法來檢測死鎖太過被動,innodb還提供了wait-for graph演算法來主動進行死鎖檢測,每當加鎖請求無法立即滿足需要並進入等待時,wait-for graph演算法都會被觸發。
1.2 wait-for graph原理
我們怎麼知道上圖中四輛車是死鎖的?他們相互等待對方的資源,而且形成環路!我們將每輛車看為一個節點,當節點1需要等待節點2的資源時,就生成一條有向邊指向節點2,最後形成一個有向圖。我們只要檢測這個有向圖是否出現環路即可,出現環路就是死鎖!這就是wait-for graph演算法。
圖3 wait for graph
innodb將各個事務看為一個個節點,資源就是各個事務佔用的鎖,當事務1需要等待事務2的鎖時,就生成一條有向邊從1指向2,最後行成一個有向圖。
1.2 innodb隔離級別、索引與鎖
死鎖檢測是死鎖發生時innodb給我們的救命稻草,我們需要它,但我們更需要的是避免死鎖發生的能力,如何儘可能避免?這需要了解innodb中的鎖。
1.2.1 鎖與索引的關係
假設我們有一張訊息表(msg),裡面有3個欄位。假設id是主鍵,token是非唯一索引,message沒有索引。
id: bigint | token: varchar(30) | message: varchar(4096) |
innodb對於主鍵使用了聚簇索引,這是一種資料儲存方式,表資料是和主鍵一起儲存,主鍵索引的葉結點儲存行資料。對於普通索引,其葉子節點儲存的是主鍵值。
圖4 聚簇索引和二級索引
下面分析下索引和鎖的關係。
1)delete from msg where id=2;
由於id是主鍵,因此直接鎖住整行記錄即可。
圖5
2)delete from msg where token=’ cvs’;
由於token是二級索引,因此首先鎖住二級索引(兩行),接著會鎖住相應主鍵所對應的記錄;
圖6
3)delete from msg where message=訂單號是多少’;
message沒有索引,所以走的是全表掃描過濾。這時表上的各個記錄都將新增上X鎖。
圖7
1.2.2 鎖與隔離級別的關係
大學資料庫原理都學過,為了保證併發運算元據的正確性,資料庫都會有事務隔離級別的概念:1)未提交讀(Read uncommitted);2)已提交讀(Read committed(RC));3)可重複讀(Repeatable read(RR));4)可序列化(Serializable)。我們較常使用的是RC和RR。
提交讀(RC):只能讀取到已經提交的資料。
可重複讀(RR):在同一個事務內的查詢都是事務開始時刻一致的,InnoDB預設級別。
我們在1.2.1節談論的其實是RC隔離級別下的鎖,它可以防止不同事務版本的資料修改提交時造成資料衝突的情況,但當別的事務插入資料時可能會出現問題。
如下圖所示,事務A在第一次查詢時得到1條記錄,在第二次執行相同查詢時卻得到兩條記錄。從事務A角度上看是見鬼了!這就是幻讀,RC級別下儘管加了行鎖,但還是避免不了幻讀。
圖8
innodb的RR隔離級別可以避免幻讀發生,怎麼實現?當然需要藉助於鎖了!
為了解決幻讀問題,innodb引入了gap鎖。
在事務A執行:update msg set message=‘訂單’ where token=‘asd’;
innodb首先會和RC級別一樣,給索引上的記錄新增上X鎖,此外,還在非唯一索引’asd’與相鄰兩個索引的區間加上鎖。
這樣,當事務B在執行insert into msg values (null,‘asd’,’hello’); commit;時,會首先檢查這個區間是否被鎖上,如果被鎖上,則不能立即執行,需要等待該gap鎖被釋放。這樣就能避免幻讀問題。
圖9
推薦一篇好文,可以深入理解鎖的原理:http://hedengcheng.com/?p=771#_Toc374698322
3 死鎖成因
瞭解了innodb鎖的基本原理後,下面分析下死鎖的成因。如前面所說,死鎖一般是事務相互等待對方資源,最後形成環路造成的。下面簡單講下造成相互等待最後形成環路的例子。
3.1不同表相同記錄行鎖衝突
這種情況很好理解,事務A和事務B操作兩張表,但出現迴圈等待鎖情況。
圖10
3.2相同表記錄行鎖衝突
這種情況比較常見,之前遇到兩個job在執行資料批量更新時,jobA處理的的id列表為[1,2,3,4],而job處理的id列表為[8,9,10,4,2],這樣就造成了死鎖。
圖11
3.3不同索引鎖衝突
這種情況比較隱晦,事務A在執行時,除了在二級索引加鎖外,還會在聚簇索引上加鎖,在聚簇索引上加鎖的順序是[1,4,2,3,5],而事務B執行時,只在聚簇索引上加鎖,加鎖順序是[1,2,3,4,5],這樣就造成了死鎖的可能性。
圖12
3.4 gap鎖衝突
innodb在RR級別下,如下的情況也會產生死鎖,比較隱晦。不清楚的同學可以自行根據上節的gap鎖原理分析下。
圖13
4 如何儘可能避免死鎖
1)以固定的順序訪問表和行。比如對第2節兩個job批量更新的情形,簡單方法是對id列表先排序,後執行,這樣就避免了交叉等待鎖的情形;又比如對於3.1節的情形,將兩個事務的sql順序調整為一致,也能避免死鎖。
2)大事務拆小。大事務更傾向於死鎖,如果業務允許,將大事務拆小。
3)在同一個事務中,儘可能做到一次鎖定所需要的所有資源,減少死鎖概率。
4)降低隔離級別。如果業務允許,將隔離級別調低也是較好的選擇,比如將隔離級別從RR調整為RC,可以避免掉很多因為gap鎖造成的死鎖。
5)為表新增合理的索引。可以看到如果不走索引將會為表的每一行記錄新增上鎖,死鎖的概率大大增大。
5 如何定位死鎖成因
下面以本文開頭的死鎖案例為例,講下如何排查死鎖成因。
1)通過應用業務日誌定位到問題程式碼,找到相應的事務對應的sql;
因為死鎖被檢測到後會回滾,這些資訊都會以異常反應在應用的業務日誌中,通過這些日誌我們可以定位到相應的程式碼,並把事務的sql給梳理出來。
start tran 1 deleteHeartCheckDOByToken 2 updateSessionUser ... commit
此外,我們根據日誌回滾的資訊發現在檢測出死鎖時這個事務被回滾。
2)確定資料庫隔離級別。
執行select @@global.tx_isolation,可以確定資料庫的隔離級別,我們資料庫的隔離級別是RC,這樣可以很大概率排除gap鎖造成死鎖的嫌疑;
3)找DBA執行下show InnoDB STATUS看看最近死鎖的日誌。
這個步驟非常關鍵。通過DBA的幫忙,我們可以有更為詳細的死鎖資訊。通過此詳細日誌一看就能發現,與之前事務相沖突的事務結構如下:
start tran 1 updateSessionUser 2 deleteHeartCheckDOByToken ... commit
這不就是圖10描述的死鎖嘛!
相關文章
- MySQL鎖等待與死鎖問題分析MySql
- MySQL死鎖問題MySql
- MySQL死鎖系列-線上死鎖問題排查思路MySql
- 手把手教你分析解決MySQL死鎖問題MySql
- MySQL:一個死鎖分析 (未分析出來的死鎖)MySql
- 故障分析 | MySQL死鎖案例分析MySql
- Mysql使用kill命令解決死鎖問題MySql
- MySQL鎖問題分析-全域性讀鎖MySql
- MySQL批量更新死鎖案例分析MySql
- 記一次 MySQL select for update 死鎖問題MySql
- MySQL:RR分析死鎖一列MySql
- MySQL死鎖分析與解決之路MySql
- MySQL死鎖系列-常見加鎖場景分析MySql
- SpringBoot Seata 死鎖問題排查Spring Boot
- 線上BUG:MySQL死鎖分析實戰MySql
- Slave SQL執行緒與PXB FTWRL死鎖問題分析SQL執行緒
- 從一個死鎖問題分析最佳化器特性
- MySQL 死鎖和鎖等待MySql
- Python | 淺談併發鎖與死鎖問題Python
- MySQL死鎖案例分析一(先delete,再insert,導致死鎖)MySqldelete
- 死鎖案例分析
- 併發技術5:死鎖問題
- 解決Oracle死鎖問題步驟Oracle
- 面試:什麼是死鎖,如何避免或解決死鎖;MySQL中的死鎖現象,MySQL死鎖如何解決面試MySql
- MySQL 死鎖解決MySql
- MySQL解決死鎖MySql
- 一次 MySQL 線上死鎖分析實戰MySql
- 一次徹底講清如何處理mysql 的死鎖問題MySql
- Oracle死鎖一例(ORA-00060),鎖表導致的業務死鎖問題Oracle
- GreatSQL 死鎖案例分析SQL
- mysql行鎖和死鎖檢測MySql
- Mysql 兩階段鎖和死鎖MySql
- SQLServer的死鎖分析(1):頁鎖SQLServer
- mysql死鎖最佳化MySql
- MySQL:死鎖一例MySql
- 【MySQL】死鎖案例之六MySql
- 【MySQL】死鎖案例之七MySql
- 【MySQL】死鎖案例之八MySql