一個Bug看InnoDB鎖機制

一個Bug引發(fā)的死鎖

最近有用戶反映,系統(tǒng)時不時會出現(xiàn)報錯的現(xiàn)象。登陸生產(chǎn)環(huán)境查看日志,發(fā)現(xiàn)MySQL出現(xiàn)了死鎖。根據(jù)報錯信息排查,在生產(chǎn)環(huán)境發(fā)現(xiàn)了如下代碼。

session.query(TblMsg.id).filter(TblMsg.key == key).with_for_update()

obj_msg = TblMsg(
    group_id=group_id,
    key=key,
    sep=sep
)
session.add(obj_msg)
session.commit()

為了使邏輯更加清晰,代碼我已經(jīng)簡化過了。經(jīng)常運行到會話提交事務時,就產(chǎn)生死鎖。下面我們就來一步步分析,產(chǎn)生死鎖的原因以及解決策略。

InnoDB行鎖的種類

首先,我們來看看InnoDB中行鎖的種類。

Recodrd Lock: 行鎖,每行級別加鎖。
Gap Lock: 間隙鎖,鎖住一個范圍,但并不包括要鎖的那個值。
Next key Lock: 行鎖和間隙鎖的結合,即鎖住范圍也鎖住要鎖的值,是Gap Lock和Next key Lock的結合。

事務的隔離級別

提及到鎖,那么就一定離不開事務的隔離級別。


lock1.png

這里我們著重關注READ COMMITEDREPEATABLE READ,MySQL默認的事務隔離級別為REPEATABLE READ。我們都知道在REPEATABLE READ這個隔離級別下,理論上是會出現(xiàn)幻讀的。為了解決這個問題InnoDB引入了間隙鎖這個概念。

Gap Lock

Gap Lock在RR隔離級別上才會生效,其他事務隔離級別不會出現(xiàn)Gap Lock。間隙鎖的出現(xiàn)還和索引有關系。列如update xxx set a=1 where a=2;這個語句。

lock2.png

現(xiàn)在主要來看看a為輔助索引的情況,借此來研究Gap Lock的技術細節(jié)。首先我們創(chuàng)建一張表。

create table `test_gap_lock`(
    id unsigned int primary key,
    number int,
    key `key_number` ("number"),
);

然后在db中插入數(shù)據(jù)
INSERT INTO `test_gap_lock`(
  `id`, `number`
)VALUE
  (1,2),
  (3,4),
  (6,5),
  (8,5),
  (10,5),
  (13,11);

在輔助索引a上存在的next-key lock為(-∞, 2], (2, 4], (4, 5], (5, 11],(11, +∞)
現(xiàn)在我們啟動兩個session

# session 1
BEGIN ;
SELECT * from `test_gap_lock` WHERE number=4 FOR UPDATE ;
# session 2
INSERT INTO `test_gap_lock`(`id`, `number`) VALUE (100, 3); # 阻塞
INSERT INTO `test_gap_lock`(`id`, `number`) VALUE (5, 5); #阻塞
INSERT INTO `test_gap_lock`(`id`, `number`) VALUE (7, 5); #成功

現(xiàn)在來解釋一下,因為number為輔助索引,現(xiàn)在手動給number為4的值加一把寫鎖時,會鎖住4附近的間隙。即(2, 4]和(4, 5]這個區(qū)間。插入number值為3的行,在這個范圍內(nèi),所以會阻塞住。

二、三次插入同樣的值,第二次插入阻塞,第三次插入成功,則和索引的排列有關。在輔助索引中,如果索引上的值相同,那么則按照聚集索引的順序進行排列。因為id=5的這次插入在id=3和id=6這兩行數(shù)據(jù)之間,所以被阻塞住。而id=7的插入,不在這個范圍內(nèi),所以能插入成功。

解決問題

有了上面的知識,這個Bug的產(chǎn)生的原因就顯而易見了。在并發(fā)的條件下,sql的執(zhí)行順序可能產(chǎn)生一下的情況。

# session 1
session.query(TblMsg.id).filter(TblMsg.key == key).with_for_update()

# session 2
session.query(TblMsg.id).filter(TblMsg.key == key).with_for_update()

# session 1 阻塞
session.add(obj_msg)
session.commit()

# session 2 阻塞
session.add(obj_msg)
session.commit()

key為表上的一個二級索引,當手動加鎖的時候,鎖的性質(zhì)變?yōu)镹EXT-KEY Lock。不僅鎖住key值,同時也鎖住間隙。session 1鎖住了相應的間隙,session 2也鎖住了相應的間隙。如果這個時候session 1鎖住的間隙,正好是session 2要插入的值。session 2鎖住的間隙,是session 1要插入的值。就會出現(xiàn)死鎖。

解決問題的辦法很簡單,有兩種策略:
1、 使用Unique key或主鍵作為篩選條件,從next-key lock退化為recode lock。
2、 事務的隔離級別從RR退回到RC,或者手動設置參數(shù)關閉gap lock。這種更改最為簡單,但可能出現(xiàn)幻讀,所以需要確定幻讀不會影響業(yè)務的正常運行。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 1背景1 1.1MVCC:Snapshot Read vs Current Read2 1.2Cluster In...
    簡小鹿奔跑ing閱讀 4,300評論 1 50
  • MySQL 加鎖處理分析 轉(zhuǎn)載2013年12月13日 16:43:55 7598 原文地址:http://hede...
    初來的雨天閱讀 525評論 0 2
  • 1. 鎖類型 鎖是數(shù)據(jù)庫區(qū)別與文件系統(tǒng)的一個關鍵特性,鎖機制用于管理對共享資源的并發(fā)訪問。InnoDB使用的鎖類型...
    butterfly100閱讀 1,237評論 0 2
  • 極簡的純粹,恰恰來自你對這件事完完全全細致全面的深度了解和領悟。你越了解的詳細,思考的透徹,你越能化簡去繁,提煉出...
    孫起凡卓雅智慧教育城閱讀 767評論 0 1
  • 悠悠而心, 夜鳴清幕, 黑而無盡, 心而不心, 腳路為伴, 氣身為伍, 慢慢經(jīng)心, 黑發(fā)為白, 白發(fā)已落, 軀已枯...
    三笑草人閱讀 140評論 0 0

友情鏈接更多精彩內(nèi)容