劃重點(diǎn)!你還在困惑MySQL中的"鎖"嗎?
基礎(chǔ)概念篇
01. 怎么認(rèn)識(shí)"鎖"
- 簡(jiǎn)單的說(shuō),鎖(locking)是數(shù)據(jù)庫(kù)中的一項(xiàng)機(jī)制,用于處理多個(gè)事務(wù)間的協(xié)同關(guān)系
- 可以把它看成是數(shù)據(jù)庫(kù)對(duì)某些記錄或數(shù)據(jù)表的一種標(biāo)記,用于指示資源當(dāng)前狀態(tài)是否被某些事務(wù)占用
02. "鎖"的分類(lèi)
- 按照加鎖思想不同,可區(qū)分樂(lè)觀鎖(optimistic locking)和悲觀鎖(pessimistic locking)——這是一個(gè)虛構(gòu)的概念
- 按照加鎖策略,可分為記錄鎖(record locking)、間隙鎖(gap locking)和臨鍵鎖(next-key locking),其中臨鍵鎖=記錄鎖+間隙鎖
- 按照加鎖粒度,可分為行鎖(row-level locking)和表鎖(table-level locking),其中InnoDB可以加行鎖,也可以加表鎖;MyISAM只能加表鎖
- 按照加鎖影響,可區(qū)分共享鎖(share locking,S鎖)和排他鎖(exclusive locking,X鎖),二者又分別稱(chēng)作讀鎖和寫(xiě)鎖
- 事務(wù)加鎖之前要先發(fā)出"請(qǐng)求",所以就產(chǎn)生了意向鎖(intention locking),相當(dāng)于是向引擎發(fā)出一個(gè)加鎖的意向:又可細(xì)分為共享意向鎖(intention share locking,IS)和排他意向鎖(intention exclusive locking,IX),請(qǐng)求成功(請(qǐng)求加鎖的目標(biāo)未被占用)則變成相應(yīng)的S鎖或X鎖,否則便處于等待狀態(tài)或者超時(shí)退出。
03. 加"鎖"過(guò)程
- 加鎖過(guò)程一般分為兩個(gè)階段,即加鎖(locking phase)和解鎖(unlocking phase),所以也叫兩階段鎖(two-phase locking)
- 鎖的作用范圍是事務(wù),所以加鎖只能在開(kāi)啟事務(wù)之后由某些SQL語(yǔ)句觸發(fā),而當(dāng)提交事務(wù)或回滾時(shí)釋放鎖
04. 給誰(shuí)加"鎖"
- 不是所有的SQL語(yǔ)句都加鎖,例如DDL(數(shù)據(jù)定義語(yǔ)言)和DCL(數(shù)據(jù)控制語(yǔ)言)因不涉及事務(wù),自然不存在鎖的問(wèn)題
- 也不是所有的DQL(特指數(shù)據(jù)查詢(xún)語(yǔ)言,形如select……)都加鎖,例如普通的select語(yǔ)句都不加鎖,而是依靠MVCC(multi-version concurrency control,即多版本并發(fā)控制)來(lái)實(shí)現(xiàn)事務(wù)的"某種"一致性
- 普通select語(yǔ)句不加鎖,如想加鎖只需在select語(yǔ)句后明確指定"for share"或"for update"即可,其中前者就是共享鎖(S鎖),也叫讀鎖;后者是排他鎖(X鎖),也叫寫(xiě)鎖
- 但是所有的DML語(yǔ)句(數(shù)據(jù)操作語(yǔ)言,insert、update和delete)都會(huì)自動(dòng)加鎖,而且加的是排他鎖(X鎖)
05. 加"鎖"目的
- 加鎖的目的是為了數(shù)據(jù)庫(kù)的穩(wěn)定性和一致性,但其副作用是降低了并發(fā)能力,所以加鎖策略往往要在一致性(consistency)和并發(fā)能力(concurrency)間折中
- 加鎖是為了權(quán)衡數(shù)據(jù)一致性和并發(fā)能力,MySQL中不加鎖實(shí)現(xiàn)這一機(jī)制的方法是MVCC,即大名鼎鼎的多版本并發(fā)控制;與之對(duì)應(yīng),加鎖實(shí)現(xiàn)的并發(fā)機(jī)制則叫做LBCC(locking-based concurrency control)
06. 加"鎖"對(duì)象
- 表鎖,是對(duì)整個(gè)表進(jìn)行鎖定,如果是虛擬的視圖(view)、觸發(fā)器(trigger),則會(huì)將其關(guān)聯(lián)的所有表進(jìn)行鎖定
- 行鎖,實(shí)際鎖的對(duì)象不是行,而是按索引鎖定,也就是說(shuō)鎖不會(huì)定位到某條記錄,而是通過(guò)限制索引來(lái)間接作用到記錄
07. "鎖"和事務(wù)
- SQL通用標(biāo)準(zhǔn)定義了事務(wù)的ACID四大屬性,即原子性Atomcity,一致性Consistency,隔離性Isolation,持久性Durability
- 為了實(shí)現(xiàn)隔離性進(jìn)而確保一致性,需要實(shí)現(xiàn)事務(wù);事務(wù)的實(shí)現(xiàn)又依賴(lài)于存儲(chǔ)引擎,MySQL的兩種常用引擎中,默認(rèn)引擎InnoDB支持事務(wù),而MyISAM則不支持
- 前面提到,普通的查詢(xún)語(yǔ)句不加任何鎖,此時(shí)innoDB引擎依靠MVCC機(jī)制實(shí)現(xiàn)數(shù)據(jù)庫(kù)的隔離性和一致性。MVCC,簡(jiǎn)單的說(shuō)就是對(duì)可能存在并發(fā)和爭(zhēng)議的記錄增加帶有版本信息的隱藏字段,例如時(shí)間戳,來(lái)確保多次查詢(xún)數(shù)據(jù)的一致性
- 一致性的狀態(tài)又具體因隔離級(jí)別不同而異,SQL92標(biāo)準(zhǔn)(數(shù)據(jù)庫(kù)通用標(biāo)準(zhǔn),非MySQL獨(dú)有)定義了四大隔離等級(jí):
a. 讀未提交(Read Uncommitted,RU),即一個(gè)事務(wù)可以讀到其他事務(wù)已操作但未提交的數(shù)據(jù),當(dāng)這個(gè)操作回滾時(shí),即發(fā)生臟讀
b. 讀已提交(Read Committed,RC),即一個(gè)事務(wù)僅能讀到其他事務(wù)已提交的數(shù)據(jù),確保這個(gè)數(shù)據(jù)是實(shí)實(shí)在在真實(shí)的數(shù)據(jù),避免了臟讀,但可能導(dǎo)致本事務(wù)窗口內(nèi)前后查詢(xún)結(jié)果不一致,即不可重復(fù)讀
c. 可重復(fù)讀(Repeatable Read,RR),即可重復(fù)讀,基于MVCC機(jī)制,在當(dāng)前事務(wù)中的首次查詢(xún)時(shí),記錄一個(gè)快照版本,同一事務(wù)期間的后續(xù)查詢(xún)均采用當(dāng)前快照版本的結(jié)果,所以即使是其他事務(wù)已提交的數(shù)據(jù),但若其快照版本在本事務(wù)首次快照版本之后,也不會(huì)讀出來(lái)。注意,這里當(dāng)前事務(wù)采集的快照"版本號(hào)"取決于首次查詢(xún)的時(shí)機(jī),而不是開(kāi)始事務(wù)的時(shí)機(jī)。
d. 串行化(Serializable, SE),嚴(yán)格限制并發(fā),多個(gè)事務(wù)間在存在數(shù)據(jù)競(jìng)爭(zhēng)時(shí)串行執(zhí)行,數(shù)據(jù)穩(wěn)定性和一致性最強(qiáng),但并發(fā)能力受到極大限制。注意,這里是指存在數(shù)據(jù)沖突時(shí)事務(wù)間串行,否則仍可并發(fā)
- 不是所有的數(shù)據(jù)庫(kù)都必須包含這4種隔離級(jí)別(例如Oracle數(shù)據(jù)庫(kù)主要支持RC和SE兩個(gè)隔離級(jí)別),不同數(shù)據(jù)庫(kù)實(shí)現(xiàn)的方式也不盡相同。MySQL支持全部4個(gè)隔離級(jí)別,默認(rèn)為RR級(jí)別
- 默認(rèn)情況下,MySQL執(zhí)行的每條SQL語(yǔ)句都是自動(dòng)提交的,如果想顯式的執(zhí)行事務(wù),有兩種方法:
- 1## 開(kāi)啟事務(wù)2種方法
- 2-- 一種是顯式開(kāi)啟事務(wù)
- 3START TRANSACTION / BEGIN
- 4-- 另一種是關(guān)閉自動(dòng)提交
- 5SET autocommit = 0
- 6
- 7## 結(jié)束事務(wù)
- 8COMMIT / ROLLBACK
- 對(duì)于未顯式開(kāi)啟事務(wù)的SQL語(yǔ)句,可將其看做是在語(yǔ)句前后分別自動(dòng)開(kāi)啟和提交事務(wù),即:
- 1select ……;
- 2等價(jià)于
- 3START TRANSACTION;
- 4selece ……;
- 5COMMIT;
08."讀象"
read phenomena,官方文檔給出的英文寫(xiě)法,未找到相關(guān)權(quán)威翻譯名詞。特指MySQL讀取過(guò)程中存在的副作用,例如臟讀、幻讀等
- read phenomena,主要是指數(shù)據(jù)庫(kù)中三種"錯(cuò)誤"的讀取結(jié)果:
- 臟讀:dirty read,即A事務(wù)讀取了B事務(wù)更改但未提交的信息,主要發(fā)生在RU隔離級(jí)別
- 不可重復(fù)讀,non-repeatable read,即由于B事務(wù)在A事務(wù)期間對(duì)數(shù)據(jù)更改并已提交,導(dǎo)致A事務(wù)前后讀取到不一致的結(jié)果
- 幻讀,phantom read,即A事務(wù)在之后的查詢(xún)中出現(xiàn)了前期未出現(xiàn)的記錄。
- 鑒于部分資料對(duì)幻讀和不可重復(fù)讀解釋很亂,這里再說(shuō)下幻讀和不可重讀區(qū)別:
- 不可重復(fù)讀,顧名思義,是指前后兩次讀取結(jié)果不一致,這里的不一致涵蓋的范圍很廣,換言之只要前后不一致就都屬于不可重復(fù)讀。造成原因主要是一項(xiàng)事務(wù)在執(zhí)行期間,其他事務(wù)對(duì)數(shù)據(jù)表進(jìn)行了更改并提交(如果未提交就能讀到那么性質(zhì)更惡劣,屬于臟讀),主要發(fā)生在RC隔離級(jí)別,因?yàn)镽C意味著"讀已提交",所以但凡其他事務(wù)已提交的數(shù)據(jù)更新該事務(wù)都能察覺(jué)到,前后結(jié)果當(dāng)然可能不一致
- 而幻讀,顧名思義,是指讀到了之前未曾發(fā)現(xiàn)的記錄,當(dāng)然,從某種意義上將之前未曾發(fā)覺(jué)肯定也屬于不可重復(fù)讀,這樣理解本身是沒(méi)錯(cuò)的,只是二者側(cè)重點(diǎn)不一樣。幻讀側(cè)重于在本事務(wù)執(zhí)行期間,其他事務(wù)插入(insert)了新的記錄,造成本事務(wù)之后讀取到了前期不曾發(fā)現(xiàn)的事務(wù),好似發(fā)生幻覺(jué)一樣,是謂幻讀。
需要指出:MySQL依靠MVCC的快照機(jī)制,某種程度上RR隔離級(jí)別已經(jīng)避免了幻讀,但仍可觸發(fā),官方文檔也給予相應(yīng)的說(shuō)明。具體請(qǐng)閱讀后面的實(shí)戰(zhàn)案例。
09. 快照讀和當(dāng)前讀
- 快照讀,snapshot read,也叫一致讀或非加鎖讀,consistent nonlocking read,指不依靠加鎖來(lái)保證查詢(xún)數(shù)據(jù)一致性,是MySQL中RR和RC級(jí)別下的默認(rèn)查詢(xún)語(yǔ)句執(zhí)行方式,通過(guò)MVCC機(jī)制實(shí)現(xiàn)按"快照"版本號(hào)執(zhí)行讀操作。RR級(jí)別和RC級(jí)別采集"快照"原則是不同的,這也是導(dǎo)致兩種隔離級(jí)別存在不同"讀象"(不可重讀或幻讀)的原因,其中:
- RR級(jí)別以進(jìn)入事務(wù)后第一次讀操作的時(shí)間作為快照版本(注意是第一次讀操作的時(shí)間,而與開(kāi)啟事務(wù)時(shí)間無(wú)關(guān)),一旦確定快照版本,則在本事務(wù)后續(xù)讀操作中就都應(yīng)用此快照結(jié)果
- RC級(jí)別是每次讀操作時(shí)均采集快照,所以當(dāng)其他事務(wù)提交后它能及時(shí)采集到新的快照
- 普通查詢(xún)語(yǔ)句中,RC級(jí)別因?yàn)榇嬖谂K讀,所以不屬于一致讀
- SE級(jí)別因?yàn)槭强考渔i(默認(rèn)對(duì)普通select語(yǔ)句加S鎖)來(lái)實(shí)現(xiàn)數(shù)據(jù)一致,能夠確保讀取到一致的結(jié)果,但已不是原原本本的一致讀
- 當(dāng)前讀,current read,也叫加鎖讀,即locking read,特指在普通查詢(xún)語(yǔ)句后增加"for share"或"for update"來(lái)指定共享讀或排他讀的讀操作,其中:
- for share,即加S鎖,允許多個(gè)事務(wù)同時(shí)獲取該S鎖,是謂共享
- for update,即加X(jué)鎖,僅供獲取到該X鎖的事務(wù)操作,是謂排他
- 由于加鎖讀是建立在事務(wù)的基礎(chǔ)上,所以必須顯式開(kāi)啟事務(wù)后,加鎖讀才有意義,否則因?yàn)槭聞?wù)的
實(shí)戰(zhàn)案例篇
以下所有案例均依托Navicat Primium12工具。初始建表語(yǔ)句:
- 1create table test(id int, name varchar(20), primary key(id));
- 2insert into test values(1, 'A');
- 3insert into test values(3, 'C');
10. 3種"讀象"
臟讀、不可重復(fù)讀和幻讀應(yīng)該是困擾很多人的一個(gè)常見(jiàn)概念問(wèn)題,尤其是后兩者的區(qū)別,這里通過(guò)幾個(gè)案例進(jìn)行闡釋說(shuō)明。
- 臟讀,dirty read
首先來(lái)看官方文檔給出的定義:
An operation that retrieves unreliable data, data that was updated by another transaction but not yet committed. It is only possible with the isolation level known as read uncommitted.
大意:某個(gè)操作中處理了由其他事務(wù)更新但尚未提交的數(shù)據(jù),這個(gè)數(shù)據(jù)是不可靠的數(shù)據(jù),僅發(fā)生于RU隔離級(jí)別。
案例:
RU存在臟讀:事務(wù)A讀到了事務(wù)B更改但未提交的數(shù)據(jù)
- 不可重復(fù)讀,non-repeatable read
官方文檔給出的定義:
The situation when a query retrieves data, and a later query within the same transaction retrieves what should be the same data, but the queries return different results (changed by another transaction committing in the meantime).
大意:在一項(xiàng)事務(wù)查詢(xún)數(shù)據(jù)期間,由于其他事務(wù)同時(shí)進(jìn)行了提交,造成其前后兩次查詢(xún)到的數(shù)據(jù)結(jié)果不一致。
案例:
RC避免了臟讀,但存在不可重復(fù)讀
- 幻讀,phantom read
A row that appears in the result set of a query, but not in the result set of an earlier query. For example, if a query is run twice within a transaction, and in the meantime, another transaction commits after inserting a new row or updating a row so that it matches the WHERE clause of the query.
大意:之前查詢(xún)的結(jié)果中不存在、但之后查詢(xún)得到的記錄稱(chēng)作是幻讀。例如,一個(gè)查詢(xún)執(zhí)行兩次,期間另一個(gè)事務(wù)進(jìn)行了插入或更新記錄并提交,導(dǎo)致前一個(gè)事務(wù)兩次查詢(xún)結(jié)果不一致。
個(gè)人觀點(diǎn),幻讀本身當(dāng)然屬于不可重復(fù)讀的一種,畢竟兩次讀取結(jié)果"不一致"。但幻讀側(cè)重的是之前沒(méi)有、之后虛幻出來(lái)了新行這種特定操作。
案例:
①,RR級(jí)別可避免RC級(jí)別中的不可重復(fù)讀問(wèn)題:
RR不存在不可重復(fù)讀數(shù)據(jù)
②,特殊情況下仍可觸發(fā)幻讀
RR級(jí)別下,特殊操作仍可觸發(fā)幻讀(更新快照)
實(shí)際上,MVCC機(jī)制只是為保證讀取結(jié)果采取快照的方式,所以能保證可重復(fù)讀,但對(duì)于執(zhí)行insert、update和delete操作時(shí),仍然會(huì)實(shí)際檢測(cè)當(dāng)前數(shù)據(jù)庫(kù)中最新的記錄狀態(tài):當(dāng)其他事務(wù)提交的最新數(shù)據(jù)與本事務(wù)中的增刪改操作符合條件時(shí),仍然會(huì)有影響。
這點(diǎn)不難理解,畢竟要保證數(shù)據(jù)庫(kù)的狀態(tài)一致性,但值得詫異的是經(jīng)過(guò)update之后,居然會(huì)更新事務(wù)中的快照版本。例如圖中所示案例,初次查詢(xún)有2條記錄,update時(shí)實(shí)際更新的是3條,但再次查詢(xún)時(shí)結(jié)果也更新成了3條。而且,更重要的是,這種現(xiàn)象并不具有普遍性:僅當(dāng)事務(wù)執(zhí)行update操作時(shí)才會(huì)更新快照版本,而對(duì)于delete和insert操作則是只檢測(cè)狀態(tài)不更新快照版本。
事務(wù)的insert操作不會(huì)更新快照版本
更一般的,進(jìn)一步測(cè)試了事務(wù)B執(zhí)行的其他增刪改操作對(duì)事務(wù)A是否更新快照版本的影響,兩兩組合,得到如下試驗(yàn)結(jié)論:
如上幻讀僅發(fā)生在其他事務(wù)插入新記錄且提交后,本事務(wù)更新數(shù)據(jù)后的再次查詢(xún)中
當(dāng)然,官方文檔對(duì)此給出了注解:
大意是說(shuō):快照讀(snapshot)僅適用于查詢(xún)語(yǔ)句,對(duì)DML(數(shù)據(jù)操縱語(yǔ)言,即增刪改操作)不適用。其他事務(wù)執(zhí)行刪除或更新操作并提交,當(dāng)前事務(wù)雖然"看不到"這些更改,但在執(zhí)行自己執(zhí)行更新或刪除操作后對(duì)其可見(jiàn)。雖然此注解足以解釋上述案例結(jié)論,但筆者實(shí)際上仍然存在前述表中的疑問(wèn)。
最后需要指出的是,MVCC機(jī)制是基于快照版本的并發(fā)控制,與之對(duì)應(yīng)的是LBCC,當(dāng)采用LBCC讀取數(shù)據(jù)時(shí),則總能讀到最新的數(shù)據(jù)。當(dāng)然,這與RR隔離級(jí)別和MVCC機(jī)制并不矛盾。
加鎖讀總是讀取最新結(jié)果,但不影響快照版本
11. 快照版本
MVCC是基于多版本的并發(fā)控制,查詢(xún)結(jié)果以快照版本為準(zhǔn)。但不同隔離級(jí)別的快照版本采集原則不一致。在RR隔離級(jí)別中,通過(guò)MVCC機(jī)制實(shí)現(xiàn)了在同一事務(wù)中的可重復(fù)讀取問(wèn)題,而且該快照是在首次查詢(xún)時(shí)采集的版本號(hào)信息,而與開(kāi)啟事務(wù)時(shí)機(jī)無(wú)關(guān)。
RR級(jí)別中首次查詢(xún)建立快照版本
而且,RR級(jí)別中一旦建立了快照版本,則在該事務(wù)的后續(xù)查詢(xún)中均采用該快照版本作為結(jié)果(當(dāng)然,通過(guò)前面的案例發(fā)現(xiàn)也有例外);與之對(duì)應(yīng)的是,RC級(jí)別中,每次查詢(xún)都采集最新的快照版本作為結(jié)果,所以自然也就存在不可重復(fù)讀的問(wèn)題。
12. 加鎖類(lèi)型
首先簡(jiǎn)單介紹記錄鎖、間隙鎖和臨鍵鎖:
- 記錄鎖
記錄鎖根據(jù)索引鎖定相應(yīng)記錄,即使相應(yīng)的表中不建立任何索引時(shí)。實(shí)際上所有InnoDB表都存在索引,當(dāng)用戶(hù)建表時(shí)未顯式設(shè)置索引時(shí),引擎會(huì)自動(dòng)建立隱藏索引,這也是InnoDB底層基于聚簇索引存取整條記錄的特性使然。
記錄鎖僅對(duì)索引滿足查詢(xún)條件的記錄加鎖
- 間隙鎖
如果說(shuō)記錄鎖是對(duì)命中的記錄進(jìn)行加鎖,那么間隙鎖是則是對(duì)查詢(xún)區(qū)間范圍內(nèi)但是不存在的記錄進(jìn)行預(yù)訂加鎖,例如下圖中假設(shè)表中不存在id=2、3的記錄,但因?yàn)闈M足查詢(xún)范圍,所以會(huì)對(duì)其加間隙鎖。
間隙鎖對(duì)滿足查詢(xún)條件的記錄間隙加鎖
顯然,間隙鎖是以犧牲一定并發(fā)性能為代價(jià)換取高一致性。實(shí)際上,這也是所有鎖在做的一件事,即在一致性和并發(fā)能力之間獲得某種均衡。
需要指出的是:
- 間隙鎖僅在范圍查詢(xún)時(shí)存在,對(duì)于等值查詢(xún)則不適用,例如上例中查詢(xún)條件改為where id=1 or id=4則不會(huì)對(duì)潛在的id=2和3加間隙鎖
- 當(dāng)查詢(xún)條件是等值查詢(xún),但查詢(xún)條件是聯(lián)合索引(在多列創(chuàng)建的索引)時(shí),也會(huì)對(duì)滿足要求的潛在記錄加間隙鎖
- 間隙鎖僅在特定隔離級(jí)別存在,RR級(jí)別中默認(rèn)有間隙鎖,而RC級(jí)別則不存在
- 臨鍵鎖
在記錄鎖和間隙鎖的基礎(chǔ)上,臨鍵鎖=記錄鎖+間隙鎖。
臨鍵鎖=記錄鎖+間隙鎖
RC隔離級(jí)別中只有記錄鎖,而沒(méi)有間隙鎖和臨鍵鎖;RR級(jí)別中如果是等值查詢(xún)則是記錄鎖,范圍查詢(xún)則是臨鍵鎖(即記錄鎖+間隙鎖),在5.6以前版本中可以通過(guò)全局參數(shù)設(shè)置是否開(kāi)啟,但在8.0版本已移除該變量。
- RC隔離級(jí)別默認(rèn)設(shè)置記錄鎖
- RR隔離級(jí)別默認(rèn)加臨鍵鎖
13. 索引類(lèi)型對(duì)加鎖影響
在明確加鎖類(lèi)型后,還需考慮不同索引對(duì)加鎖的影響。首先指出,在InnoDB引擎下即使創(chuàng)建表時(shí)不顯式指定索引,引擎也會(huì)自動(dòng)生成隱藏索引用于聚簇存儲(chǔ)記錄數(shù)據(jù)。基于此,索引對(duì)加鎖的影響有如下幾種情況(引自官方文檔):
- 一致讀(即快照讀,非加鎖讀,基于MVCC),除SE隔離級(jí)別外,其他隔離級(jí)別均不加任何鎖
- 當(dāng)前讀(加鎖讀,for share或for update),對(duì)所有滿足條件的記錄加鎖,同時(shí)釋放不滿足條件的索。對(duì)于某些復(fù)雜語(yǔ)句,例如含有Union語(yǔ)句時(shí),由于在匯總結(jié)果時(shí)涉及到臨時(shí)表,所以對(duì)于不滿足查詢(xún)條件的記錄不會(huì)立即釋放鎖。同時(shí),加記錄鎖還是臨鍵鎖要取決于索引類(lèi)型和查詢(xún)條件,只有當(dāng)對(duì)應(yīng)唯一索引下的等值查詢(xún)時(shí),才只加記錄鎖,否則會(huì)升級(jí)為臨鍵鎖
- update語(yǔ)句會(huì)對(duì)每條滿足記錄的語(yǔ)句加臨鍵鎖(X鎖),但滿足唯一索引和等值查詢(xún)時(shí),只加記錄鎖
- delete語(yǔ)句加鎖原則與update語(yǔ)句一致
- insert語(yǔ)句只對(duì)插入行加記錄鎖(X鎖),而沒(méi)有任何間隙鎖。實(shí)際上,insert語(yǔ)句是先加意向鎖,請(qǐng)求成功才去插入,否則也不會(huì)阻塞其他事務(wù)。特殊情況下,當(dāng)多個(gè)事務(wù)同時(shí)insert相同索引記錄時(shí),會(huì)發(fā)生索引重復(fù)沖突,進(jìn)而可能造成死鎖。詳見(jiàn)下一節(jié)。
不同類(lèi)型下的加鎖分析詳見(jiàn)文末參考資料2中文檔,講解充分,受到廣泛轉(zhuǎn)發(fā)引用,這里個(gè)人就不班門(mén)弄斧了。
14. 鎖競(jìng)爭(zhēng)和死鎖
一般來(lái)說(shuō),鎖具有排他性。如果是共享鎖(S鎖),可以和另一個(gè)共享鎖(S鎖)同時(shí)擁有,但無(wú)法和一個(gè)排他鎖(X鎖)同時(shí)擁有;而對(duì)于一個(gè)X鎖,則無(wú)法跟任何其他鎖并發(fā)。當(dāng)多個(gè)事務(wù)企圖同時(shí)占用某一資源需要加鎖時(shí),就有可能發(fā)生鎖競(jìng)爭(zhēng)甚至死鎖。
- 鎖競(jìng)爭(zhēng),當(dāng)多個(gè)事務(wù)同時(shí)企圖占有同一資源、但只是時(shí)間上沖突而資源占用上并不沖突時(shí),會(huì)發(fā)生鎖競(jìng)爭(zhēng):
多個(gè)事務(wù)競(jìng)爭(zhēng)同一資源
在上述案例中,三個(gè)事務(wù)依次請(qǐng)求對(duì)數(shù)據(jù)表加X(jué)鎖,其中事務(wù)A成功請(qǐng)求,事務(wù)B和事務(wù)C會(huì)處于等待。當(dāng)事務(wù)A提交事務(wù)后,雖然事務(wù)B和事務(wù)C處于同時(shí)競(jìng)爭(zhēng)加鎖狀態(tài),但由于MySQL對(duì)事務(wù)調(diào)度的FIFO(First In First Out,先入先出)特性,二者不會(huì)發(fā)生死鎖,而是優(yōu)先滿足事務(wù)B加鎖請(qǐng)求,待事務(wù)B提交事務(wù)后再滿足事務(wù)C的加鎖請(qǐng)求。
- 死鎖,與鎖競(jìng)爭(zhēng)相似而又不同的是,死鎖也是發(fā)生在多個(gè)事務(wù)同時(shí)競(jìng)爭(zhēng)同一資源,但是這些資源不能簡(jiǎn)單通過(guò)時(shí)間先后得以解決,而是存在邏輯上的沖突:
①,鎖競(jìng)爭(zhēng)+索引重復(fù)沖突造成死鎖:
三個(gè)事務(wù)競(jìng)爭(zhēng)資源存在索引重復(fù)
這個(gè)案例與鎖競(jìng)爭(zhēng)中的例子類(lèi)似但又不同:假設(shè)事務(wù)A、事務(wù)B和事務(wù)C同時(shí)請(qǐng)求插入一條數(shù)據(jù)(插入語(yǔ)句都是加X(jué)鎖),此時(shí)不僅僅是因?yàn)榧渔i沖突,還存在索引重復(fù)的問(wèn)題,此時(shí)一旦事務(wù)A回滾釋放鎖后,事務(wù)B和事務(wù)C則會(huì)陷入死鎖。這是一種特殊的死鎖觸發(fā)原因。
②,競(jìng)爭(zhēng)同一資源出現(xiàn)死循環(huán):
兩個(gè)事務(wù)先競(jìng)爭(zhēng),后死鎖
在這個(gè)案例中,先是事務(wù)A和事務(wù)B分別對(duì)id=1和id=2的記錄加X(jué)鎖,然后事務(wù)A繼續(xù)對(duì)id=2的記錄請(qǐng)求加鎖時(shí),因?yàn)樵撚涗浺驯皇聞?wù)B占有,所以事務(wù)A只能等待;但此時(shí)事務(wù)B又企圖對(duì)事務(wù)A已經(jīng)占有的id=1記錄加X(jué)鎖,造成事務(wù)A和事務(wù)B在各自占有一定資源的基礎(chǔ)上分別企圖占用對(duì)方已加鎖的資源,邏輯上沖突,騎虎難下,引擎不可能通過(guò)時(shí)間調(diào)度得以解決,故而發(fā)生死鎖。
發(fā)生死鎖后,引擎會(huì)根據(jù)相關(guān)的事務(wù)間的重要程度(包括占用資源多少、時(shí)間先后等)來(lái)選擇一個(gè)進(jìn)行回滾:例如上例中,事務(wù)A先于事務(wù)B請(qǐng)求加X(jué)鎖,可將事務(wù)B看成是直接造成死鎖的原因,所以選擇對(duì)B進(jìn)行回滾,而允許A加鎖成功。
如果能看到這里,相信應(yīng)該已對(duì)MySQL中鎖機(jī)制有較為全面的了解,那就賞個(gè)轉(zhuǎn)發(fā)或者在看吧!