MySQL InnoDB中的鎖機(jī)制深入講解
寫在前面
數(shù)據(jù)庫本質(zhì)上是一種共享資源,因此在最大程度提供并發(fā)訪問性能的同時(shí),仍需要確保每個(gè)用戶能以一致的方式讀取和修改數(shù)據(jù)。鎖機(jī)制(Locking)就是解決這類問題的最好武器。
首先新建表 test,其中 id 為主鍵,name 為輔助索引,address 為唯一索引。
CREATE TABLE `test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` int(11) NOT NULL, `address` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idex_unique` (`address`), KEY `idx_index` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
INSERT 方法中的行鎖

可見,如果兩個(gè)事務(wù)先后對(duì)主鍵相同的行記錄執(zhí)行 INSERT 操作,因?yàn)槭聞?wù) A 先拿到了行鎖,事務(wù) B 只能等待直到事務(wù) A 提交后行鎖被釋放。同理,如果針對(duì)唯一索引字段 address 進(jìn)行插入操作,也需要獲取行鎖,圖同主鍵插入過程類似,不再重復(fù)。
但是,如果兩個(gè)事務(wù)都針對(duì)輔助索引字段 name 進(jìn)行插入,不需要等待獲取鎖,因?yàn)檩o助索引字段即使值相同,在數(shù)據(jù)庫中也是操作不同的記錄行,不會(huì)沖突。
Update 方法與 Insert 方法結(jié)果類似。
SELECT FOR UPDATE 下的表鎖與行鎖

事務(wù) A SELECT FOR UPDATE 語句會(huì)拿到表 test 的 Table Lock,此時(shí)事務(wù) B 去執(zhí)行插入操作會(huì)阻塞,直到事務(wù) A 提交釋放表鎖后,事務(wù) B 才能獲取對(duì)應(yīng)的行鎖執(zhí)行插入操作。
但是如果事務(wù) A 的 SELECT FOR UPDATE 語句緊跟 WHERE id = 1 的話,那么這條語句只會(huì)獲取行鎖,不會(huì)是表鎖,此時(shí)不阻塞事務(wù) B 對(duì)于其他主鍵的修改操作
輔助索引下的間隙鎖
先看下 test 表下的數(shù)據(jù)情況:
mysql> select * from test; +----+------+---------+ | id | name | address | +----+------+---------+ | 3 | 1 | 3 | | 6 | 1 | 2 | | 7 | 2 | 4 | | 8 | 10 | 5 | +----+------+---------+ 4 rows in set (0.00 sec)
間隙鎖可以說是行鎖的一種,不同的是它鎖住的是一個(gè)范圍內(nèi)的記錄,作用是避免幻讀,即區(qū)間數(shù)據(jù)條目的突然增減。解決辦法主要是:
- 防止間隙內(nèi)有新數(shù)據(jù)被插入,因此叫間隙鎖
- 防止已存在的數(shù)據(jù),在更新操作后成為間隙內(nèi)的數(shù)據(jù)(例如更新 id = 7 的 name 字段為 1,那么 name = 1 的條數(shù)就從 2 變?yōu)?3)
InnoDB 自動(dòng)使用間隙鎖的條件為:
- Repeatable Read 隔離級(jí)別,這是 MySQL 的默認(rèn)工作級(jí)別
- 檢索條件必須有索引(沒有索引的話會(huì)走全表掃描,那樣會(huì)鎖定整張表所有的記錄)
當(dāng) InnoDB 掃描索引記錄的時(shí)候,會(huì)首先對(duì)選中的索引行記錄加上行鎖,再對(duì)索引記錄兩邊的間隙(向左掃描掃到第一個(gè)比給定參數(shù)小的值, 向右掃描掃描到第一個(gè)比給定參數(shù)大的值, 以此構(gòu)建一個(gè)區(qū)間)加上間隙鎖。如果一個(gè)間隙被事務(wù) A 加了鎖,事務(wù) B 是不能在這個(gè)間隙插入記錄的。
我們這里所說的 “間隙鎖” 其實(shí)不是 GAP LOCK,而是 RECORD LOCK + GAP LOCK,InnoDB 中稱之為 NEXT_KEY LOCK
下面看個(gè)例子,我們建表時(shí)指定 name 列為輔助索引,目前這列的取值有 [1,2,10]。間隙范圍有 (-∞, 1]、[1,1]、[1,2]、[2,10]、[10, +∞)
Round 1:
- 事務(wù) A SELECT ... WHERE name = 1 FOR UPDATE;
- 對(duì) (-∞, 2) 增加間隙鎖
- 事務(wù) B INSERT ... name = 1 阻塞
- 事務(wù) B INSERT ... name = -100 阻塞
- 事務(wù) B INSERT ... name = 2 成功
- 事務(wù) B INSERT ... name = 3 成功
Round 2:
- 事務(wù) A SELECT ... WHERE name = 2 FOR UPDATE;
- 對(duì) [1, 10) 增加間隙鎖
- 事務(wù) B INSERT ... name = 1 阻塞
- 事務(wù) B INSERT ... name = 9 阻塞
- 事務(wù) B INSERT ... name = 10 成功
- 事務(wù) B INSERT ... name = 0 成功
Round 3:
- 事務(wù) A SELECT ... WHERE name <= 2 FOR UPDATE;
- 對(duì) (-∞, +∞) 增加間隙鎖
- 事務(wù) B INSERT ... name = 3 阻塞
- 事務(wù) B INSERT ... name = 300 阻塞
- 事務(wù) B INSERT ... name = -300 阻塞
InnoDB 鎖機(jī)制總結(jié)

參考資料
- 《MySQL 技術(shù)內(nèi)幕 InnoDB 存儲(chǔ)引擎》第二版 姜承堯著
- About MySQL InnoDB's Lock
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
MySql用DATE_FORMAT截取DateTime字段的日期值
MySql截取DateTime字段的日期值可以使用DATE_FORMAT來格式化,使用方法如下2014-08-08
mysql服務(wù)1067錯(cuò)誤多種解決方案分享
今天我的mysql服務(wù)器突然出來了1067錯(cuò)誤提示,無法正常啟動(dòng)了,我今天從網(wǎng)上找尋了大量的解決mysql服務(wù)1067錯(cuò)誤的辦法,有需要的朋友可以看看2012-03-03
mysql5.7同時(shí)使用group by和order by報(bào)錯(cuò)問題
這篇文章主要介紹了mysql5.7同時(shí)使用group by和order by報(bào)錯(cuò)的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08
MySQL導(dǎo)入導(dǎo)出助手類庫MysqlHelper安裝使用
這篇文章主要為大家介紹了MySQL導(dǎo)入導(dǎo)出助手類庫MysqlHelper安裝使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09

