詳解MySql中InnoDB存儲引擎中的各種鎖
什么是鎖
現(xiàn)實(shí)生活中的鎖是為了保護(hù)你的私有物品,在數(shù)據(jù)庫中鎖是為了解決資源爭搶的問題,鎖是數(shù)據(jù)庫系統(tǒng)區(qū)別于文件系統(tǒng)的一個(gè)關(guān)鍵特性。鎖機(jī)制用于管理對共享資源的并發(fā)訪。
數(shù)據(jù)庫系統(tǒng)使用鎖是為了支持對共享資源進(jìn)行并發(fā)訪問,提供數(shù)據(jù)的完整性和一致性
InnoDB存儲引擎區(qū)別于MyISAM的兩個(gè)重要特征就是:InnoDB存儲引擎支持事務(wù)和行級別的鎖,MyISAM只支持表級別的鎖
InnoDB存儲引擎中的鎖
InnoDB存儲引擎實(shí)現(xiàn)了如下兩種標(biāo)準(zhǔn)的行級鎖:
共享鎖(S Lock),允許事務(wù)讀一行數(shù)據(jù)排他鎖(X Lock),允許事務(wù)刪除或更新一行數(shù)據(jù)
鎖的兼容性
| - | X | S |
|---|---|---|
| X | 不兼容 | 不兼容 |
| S | 不兼容 | 兼容 |
可以看到,X排他鎖不與其他鎖兼容,S共享鎖只與S兼容
此外,InnoDB存儲引擎支持多粒度(granular)鎖定,這種鎖定允許事務(wù)在行級上的鎖和表級上的鎖同時(shí)存在
為了支持在不同粒度上進(jìn)行加鎖操作,InnoDB存儲引擎支持一種額外的鎖方式,稱之為意向鎖(Intention Lock)。
意向鎖是將鎖定的對象分為多個(gè)層次,意向鎖意味著事務(wù)希望在更細(xì)粒度(fine granularity)上進(jìn)行加鎖

如上圖,數(shù)據(jù)庫從上到下可以分為數(shù)據(jù)庫、表、頁、記錄四個(gè)層次,行記錄是最細(xì)粒度的鎖,我們在獲取行鎖的時(shí)候,需要從上到下各個(gè)級別分別進(jìn)行鎖定,最后才能獲取到行鎖。比如,你要獲取行記錄x的鎖,需要先在數(shù)據(jù)庫、表、頁上加意向鎖IX,其中任何一方需要等待鎖,會造成行鎖的等待
InnoDB存儲引擎支持的意向鎖即為表級別的鎖。支持兩種意向鎖
- 意向共享鎖(IS Lock),事務(wù)想要獲得一張表中某幾行的共享鎖
- 意向排他鎖(IX Lock),事務(wù)想要獲得一張表中某幾行的排他鎖
由于InnoDB存儲引擎支持的是行級別的鎖,因此意向鎖其實(shí)不會阻塞除全表掃以外的任何請求。故表級意向鎖與行級鎖的兼容性如表所示
| - | IS | IX | S | X |
|---|---|---|---|---|
| IS | 兼容 | 兼容 | 兼容 | 不兼容 |
| IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
| S | 兼容 | 不兼容 | 兼容 | 不兼容 |
| X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
一致性非鎖定讀
一致性的非鎖定讀(consistent nonlocking read)是指InnoDB存儲引擎通過行多版本控制(multi versioning)的方式來讀取當(dāng)前執(zhí)行時(shí)間數(shù)據(jù)庫中行的數(shù)據(jù)。
如果讀取的行正在執(zhí)行DELETE或UPDATE操作,這時(shí)讀取操作不會因此去等待行上鎖的釋放。
相反地,InnoDB存儲引擎會去讀取行的一個(gè)快照數(shù)據(jù)
快照數(shù)據(jù)是指該行的之前版本的數(shù)據(jù),該實(shí)現(xiàn)是通過undo段來完成。而undo用來在事務(wù)中回滾數(shù)據(jù),因此快照數(shù)據(jù)本身是沒有額外的開銷。此外,讀取快照數(shù)據(jù)是不需要上鎖的,因?yàn)闆]有事務(wù)需要對歷史的數(shù)據(jù)進(jìn)行修改操作

快照數(shù)據(jù)其實(shí)就是當(dāng)前行數(shù)據(jù)之前的歷史版本,每行記錄可能有多個(gè)版本。如上圖,記錄B就有多個(gè)歷史的快照版本
這就是大名鼎鼎的MVCC
多版本并發(fā)控制(Multi Version Concurrency Control,MVCC)是指一個(gè)行記錄有多個(gè)快照版本,由多個(gè)快照版本引發(fā)的并發(fā)控制,叫做多版本并發(fā)控制
那這么多歷史的快速版本,訪問的時(shí)候該用哪一個(gè)呢?
- 在READ COMMITTED事務(wù)隔離級別下,非一致性讀總是讀取被鎖定行的最新一份快照數(shù)據(jù)
- 在REPEATABLE READ事務(wù)隔離級別下,非一致性讀總是讀取事務(wù)開始時(shí)的行數(shù)據(jù)版本
由此可見,不同的事務(wù)隔離級別在MVCC的處理上還不一樣
一致性鎖定讀
在默認(rèn)配置下,即事務(wù)的隔離級別為REPEATABLE READ模式下,InnoDB存儲引擎的SELECT操作使用一致性非鎖定讀
但是在某些情況下,用戶需要顯式地對數(shù)據(jù)庫讀取操作進(jìn)行加鎖以保證數(shù)據(jù)邏輯的一致性
InnoDB存儲引擎對于SELECT語句支持兩種一致性的鎖定讀(locking read)操作:
- SELECT…FOR UPDATE 對讀取的行記錄加一個(gè)X鎖,其他事務(wù)不能對已鎖定的行加上任何鎖
- SELECT…LOCK IN SHARE MODE對讀取的行記錄加一個(gè)S鎖,其他事務(wù)可以向被鎖定的行加S鎖,但是如果加X鎖,則會被阻塞
對于一致性非鎖定讀,即使讀取的行已被執(zhí)行了SELECT…FOR UPDATE,也是可以進(jìn)行讀取的
一致性鎖定讀則需要檢查被讀取的行上有沒有互斥的鎖,假如有互斥的鎖存在就需要等待鎖的釋放
鎖的算法
行鎖的3種算法
- Record Lock:單個(gè)行記錄上的鎖
- Gap Lock:間隙鎖,鎖定一個(gè)范圍,但不包含記錄本身
- Next-Key Lock∶Gap Lock+Record Lock,鎖定一個(gè)范圍,并且鎖定記錄本身
Record Lock總是會去鎖住索引記錄,如果InnoDB存儲引擎表在建立的時(shí)候沒有設(shè)置任何一個(gè)索引,那么這時(shí)InnoDB存儲引擎會使用隱式的主鍵來進(jìn)行鎖定
Gap Lock的作用是為了阻止多個(gè)事務(wù)將記錄插入到同一范圍內(nèi),而這會導(dǎo)致幻讀問題的產(chǎn)生
用戶可以通過以下兩種方式來顯式地關(guān)閉Gap Lock:
- 將事務(wù)的隔離級別設(shè)置為READ COMMITTED
- 將參數(shù)innodb_locks_unsafe_for_binlog設(shè)置為1
在上述的配置下,除了外鍵約束和唯一性檢查依然需要的Gap Lock,其余情況僅使用RecordLock進(jìn)行鎖定。
但需要牢記的是,上述設(shè)置破壞了事務(wù)的隔離性,并且對于replication,可能會導(dǎo)致主從數(shù)據(jù)的不一致。
Next-Key Lock是結(jié)合了Gap Lock和Record Lock的一種鎖定算法,在Next-Key Lock算法下,InnoDB對于行的查詢都是采用這種鎖定算法
當(dāng)查詢的索引含有唯一屬性時(shí),InnoDB存儲引擎會對Next-Key Lock進(jìn)行優(yōu)化,將其降級為Record Lock,即僅鎖住索引本身,而不是范圍
幻像問題
幻像問題(Phantom Problem)是指在同一事務(wù)下,連續(xù)執(zhí)行兩次同樣的SQL語句可能導(dǎo)致不同的結(jié)果,第二次的SQL語句可能會返回之前不存在的行
看下面的場景:
- 表t由1、2、5這三個(gè)值組成
- 執(zhí)行select * from t where a > 2 for update;
- 上述事務(wù)T1并沒有提交,那么此時(shí)另一個(gè)事務(wù)T2插入4這個(gè)值,并且數(shù)據(jù)庫允許這個(gè)操作
- 那么事務(wù)T1再執(zhí)行上述查詢,就得到4、5兩筆記錄,跟第一次得到的結(jié)果不一樣,違反了事務(wù)的隔離性
InnoDB存儲引擎采用Next-Key Locking的算法避免幻像問題。對于上述的SQL語句select * from t where a > 2 for update,其鎖住的不是5這單個(gè)值,而是對(2,+∞)這個(gè)范圍加了X鎖。因此任何對于這個(gè)范圍的插入都是不被允許的,從而避免幻像問題
InnoDB存儲引擎默認(rèn)的事務(wù)隔離級別是REPEATABLE READ,在該隔離級別下,其采用Next-Key Locking的方式來加鎖
而在事務(wù)隔離級別READ COMMITTED下,其僅采用RecordLock行鎖
鎖的問題
臟讀
臟讀指的就是在不同的事務(wù)下,當(dāng)前事務(wù)可以讀到另外事務(wù)未提交的數(shù)據(jù),簡單來說就是可以讀到臟數(shù)據(jù)
臟讀發(fā)生的條件是需要事務(wù)的隔離級別為READ UNCOMMITTED,而目前絕大部分的數(shù)據(jù)庫都至少設(shè)置成READCOMMITTED。
InnoDB存儲引擎默認(rèn)的事務(wù)隔離級別為READ REPEATABLE,Microsoft SQLServer數(shù)據(jù)庫為READ COMMITTED,Oracle數(shù)據(jù)庫同樣也是READ COMMITTED
不可重復(fù)讀
不可重復(fù)讀是指在一個(gè)事務(wù)內(nèi)多次讀取同一數(shù)據(jù)集合。在這個(gè)事務(wù)還沒有結(jié)束時(shí),另外一個(gè)事務(wù)也訪問該同一數(shù)據(jù)集合,并做了一些DML操作。因此,在第一個(gè)事務(wù)中的兩次讀數(shù)據(jù)之間,由于第二個(gè)事務(wù)的修改,那么第一個(gè)事務(wù)兩次讀到的數(shù)據(jù)可能是不一樣的。這樣就發(fā)生了在一個(gè)事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的情況,這種情況稱為不可重復(fù)讀
不可重復(fù)讀和臟讀的區(qū)別是:臟讀是讀到未提交的數(shù)據(jù),而不可重復(fù)讀讀到的卻是已經(jīng)提交的數(shù)據(jù),但是其違反了數(shù)據(jù)庫事務(wù)一致性的要求
一般來說,不可重復(fù)讀的問題是可以接受的,因?yàn)槠渥x到的是已經(jīng)提交的數(shù)據(jù),本身并不會帶來很大的問題。因此,很多數(shù)據(jù)庫廠商(如Oracle、Microsoft SQL Server)將其數(shù)據(jù)庫事務(wù)的默認(rèn)隔離級別設(shè)置為READ COMMITTED,在這種隔離級別下允許不可重復(fù)讀的現(xiàn)象
在InnoDB存儲引擎中,通過使用Next-Key Lock算法來避免不可重復(fù)讀的問題。
在MySQL官方文檔中將不可重復(fù)讀的問題定義為Phantom Problem,即幻像問題。在Next-Key Lock算法下,對于索引的掃描,不僅是鎖住掃描到的索引,而且還鎖住這些索引覆蓋的范圍(gap)。
因此在這個(gè)范圍內(nèi)的插入都是不允許的。這樣就避免了另外的事務(wù)在這個(gè)范圍內(nèi)插入數(shù)據(jù)導(dǎo)致的不可重復(fù)讀的問題。因此,InnoDB存儲引擎的默認(rèn)事務(wù)隔離級別是READ REPEATABLE,采用Next-Key Lock算法,避免了不可重復(fù)讀的現(xiàn)象
丟失更新
丟失更新是另一個(gè)鎖導(dǎo)致的問題,簡單來說其就是一個(gè)事務(wù)的更新操作會被另一個(gè)事務(wù)的更新操作所覆蓋,從而導(dǎo)致數(shù)據(jù)的不一致
- 事務(wù)T1將行記錄r更新為v1,但是事務(wù)T1并未提交
- 與此同時(shí),事務(wù)T2將行記錄r更新為v2,事務(wù)T2未提交
- 事務(wù)T1提交
- 事務(wù)T2提交
在當(dāng)前數(shù)據(jù)庫的任何隔離級別下,都不會導(dǎo)致數(shù)據(jù)庫理論意義上的丟失更新問題。這是因?yàn)?,即使是READ UNCOMMITTED的事務(wù)隔離級別,對于行的DML操作,需要對行或其他粗粒度級別的對象加鎖
死鎖
死鎖是指兩個(gè)或兩個(gè)以上的事務(wù)在執(zhí)行過程中,因爭奪鎖資源而造成的一種互相等待的現(xiàn)象
解決死鎖問題最簡單的一種方法是超時(shí),即當(dāng)兩個(gè)事務(wù)互相等待時(shí),當(dāng)一個(gè)等待時(shí)間超過設(shè)置的某一閾值時(shí),其中一個(gè)事務(wù)進(jìn)行回滾,另一個(gè)等待的事務(wù)就能繼續(xù)進(jìn)行。在InnoDB存儲引擎中,參數(shù)innodb_lock_wait_timeout用來設(shè)置超時(shí)的時(shí)間
超時(shí)機(jī)制雖然簡單,但是其僅通過超時(shí)后對事務(wù)進(jìn)行回滾的方式來處理,或者說其是根據(jù)FIFO的順序選擇回滾對象。但若超時(shí)的事務(wù)所占權(quán)重比較大,如事務(wù)操作更新了很多行,占用了較多的undo log,這時(shí)采用FIFO的方式,就顯得不合適了,因?yàn)榛貪L這個(gè)事務(wù)的時(shí)間相對另一個(gè)事務(wù)所占用的時(shí)間可能會很多
因此,除了超時(shí)機(jī)制,當(dāng)前數(shù)據(jù)庫還都普遍采用wait-for graph(等待圖)的方式來進(jìn)行死鎖檢測。
較之超時(shí)的解決方案,這是一種更為主動的死鎖檢測方式。InnoDB存儲引擎也采用的這種方式。
wait-for graph要求數(shù)據(jù)庫保存以下兩種信息
- 鎖的信息鏈表
- 事務(wù)等待鏈表
wait-for graph的死鎖檢測通常采用深度優(yōu)先的算法實(shí)現(xiàn),通常來說InnoDB存儲引擎選擇回滾undo量最小的事務(wù)
到此這篇關(guān)于詳解MySql中InnoDB存儲引擎中的各種鎖的文章就介紹到這了,更多相關(guān)詳解MySql中InnoDB存儲引擎中的各種鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mysql?InnoDB引擎中的數(shù)據(jù)頁結(jié)構(gòu)詳解
這篇文章主要為大家介紹了Mysql?InnoDB引擎中的數(shù)據(jù)頁結(jié)構(gòu)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
MySQL?原理優(yōu)化之Group?By的優(yōu)化技巧
這篇文章主要介紹了MySQL?原理優(yōu)化之Group?By的優(yōu)化技巧,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08
MySQL數(shù)據(jù)庫可以用任意ip連接訪問的方法
本文主要介紹了MySQL數(shù)據(jù)庫可以用任意ip連接訪問的方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Mysql賬號管理與引擎相關(guān)功能實(shí)現(xiàn)流程
Mysql中的每一種技術(shù)都使用不同的存儲機(jī)制、索引技巧、鎖定水平、并且最終提供廣泛的不同功能和能力。通過選擇不同的技術(shù),你能夠獲得額外的速度或者功能,從而改善應(yīng)用的整體功能。這些不同的技術(shù)以及配套的相關(guān)功能在MySQL中被稱作存儲引擎2022-10-10
SQL優(yōu)化老出錯(cuò),那是你沒弄明白MySQL解釋計(jì)劃用法
本篇文章講的是SQL優(yōu)化老出錯(cuò),那是你沒弄明白MySQL解釋計(jì)劃用法,有興趣的小伙伴速度來看看吧,希望本篇文章能夠幫助到你2021-11-11

