1.lock與latch
latch一般稱為閂鎖(輕量級(jí)的鎖),因?yàn)槠湟箧i定的時(shí)間必須非常短戈抄。若持續(xù)時(shí)間長(zhǎng)离唬,則應(yīng)用的性能會(huì)非常差。在InnoDB總划鸽,latch又可以分為mutex(互斥量)和rwlock(讀寫鎖)输莺。其目的是用來保證并發(fā)線程操作臨界資源的正確性,并且通常沒有死鎖檢測(cè)的機(jī)制裸诽。
lock的對(duì)象是事務(wù)嫂用,用來鎖定的是數(shù)據(jù)庫(kù)中的對(duì)象,如表丈冬、頁(yè)嘱函、行。并且一般lock的對(duì)象僅在事務(wù)commit或rollback后進(jìn)行釋放(不同事務(wù)隔離級(jí)別釋放的時(shí)間可能不同)埂蕊。lock是有死鎖機(jī)制的往弓。
2.InnoDB存儲(chǔ)引擎中的鎖
①鎖的類型
InnoDB實(shí)現(xiàn)了兩種表中的行級(jí)鎖:
- 共享鎖(S Lock),允許事務(wù)讀一行數(shù)據(jù)蓄氧。
- 排他鎖(X Lock)亮航,允許事務(wù)刪除或更新一行數(shù)據(jù)。
InnoDB支持多粒度鎖定匀们,這種鎖定允許事務(wù)在行級(jí)上的鎖和表級(jí)上的鎖同事存在。為了支持在不同粒度上進(jìn)行加鎖操作准给,InnoDB支持一種額外的鎖方式泄朴,稱之為意向鎖(Intention Lock)。
InnoDB支持意向鎖設(shè)計(jì)比較簡(jiǎn)練露氮,其意向鎖即為表級(jí)別的鎖祖灰。支持兩種意向鎖:
- 意向共享鎖(IS Lock),事務(wù)想要獲得一張表中某幾行的共享鎖畔规。
- 意向排他鎖(IX Lock)局扶,事務(wù)想要獲得一張表中某幾行的排他鎖。
從InnoDB 1.0開始,在INFORMATION_SCHEMA架構(gòu)下添加了表INNODB_TRX三妈、INNODB_LOCKS(lock_data值并非是“可信”的)畜埋、INNODB_LOCK_WAITS。
②一致性非鎖定讀(consistent nonlocking read)
指InnoDB通過行多版本控制(multi versioning)的方式來讀取當(dāng)前執(zhí)行時(shí)間數(shù)據(jù)庫(kù)中行的數(shù)據(jù)畴蒲。不需要等待訪問的行上X鎖的釋放悠鞍。
如果讀取的行正在執(zhí)行DELETE或UPDATE操作,這時(shí)讀取操作不會(huì)因此去等待行上鎖的釋放模燥。相反地咖祭,InnoDB會(huì)去讀取行的一個(gè)快照數(shù)據(jù)(該行之前版本的數(shù)據(jù),該實(shí)現(xiàn)是通過undo段來完成蔫骂。讀取快照信息不需要加鎖么翰,因?yàn)闆]有事務(wù)需要對(duì)歷史數(shù)據(jù)進(jìn)行修改)。
不同的事務(wù)隔離級(jí)別讀取的方式不同辽旋,READ COMMITTED和REPEATABLE READ(InnoDB默認(rèn)事務(wù)隔離級(jí)別)下浩嫌,InnoDB使用非鎖定的一致性讀,但對(duì)于快照數(shù)據(jù)的定義卻不同戴已。
- READ COMMITTED總是讀取被鎖定行的最新一份快照數(shù)據(jù)固该。
- REPEATABLE READ總是讀取事務(wù)開始時(shí)的行數(shù)據(jù)版本。
③一致性鎖定讀
在某些情況下糖儡,用戶需要顯式地對(duì)數(shù)據(jù)庫(kù)讀操作進(jìn)行加鎖以保證數(shù)據(jù)邏輯的一致性伐坏。這需要數(shù)據(jù)庫(kù)支持加鎖語句,即使是對(duì)于select的只讀操作握联。InnoDB對(duì)select語句支持兩種一致性的鎖定讀(locking read)操作:
- select ... for update (對(duì)讀取的行記錄加一個(gè)X鎖)
- select ... lock in share mode (對(duì)讀取的行記錄加一個(gè)S鎖)
對(duì)于一致性非鎖定讀桦沉,即使讀取的行已經(jīng)被執(zhí)行for update,也是可以進(jìn)行讀取的金闽。
for update纯露、lock in share mode必須在一個(gè)事務(wù)中,當(dāng)事務(wù)提交了代芜,鎖也就釋放了埠褪。
④自增長(zhǎng)與鎖
在InnoDB的內(nèi)存結(jié)構(gòu)中,對(duì)每個(gè)含有自增長(zhǎng)值的表都有一個(gè)自增長(zhǎng)計(jì)數(shù)器(auto-increment counter)挤庇。當(dāng)對(duì)含有自增長(zhǎng)計(jì)數(shù)器的表進(jìn)行插入操作時(shí)钞速,執(zhí)行類似以下語句得到計(jì)數(shù)器的值:
select max(auto_inc_col) from t for update;
插入操作會(huì)根據(jù)這個(gè)自增長(zhǎng)的計(jì)數(shù)器值加1賦予自增長(zhǎng)列。這個(gè)實(shí)現(xiàn)方式稱做AUTO-INC Locking嫡秕。這種鎖起始是采用一種特殊的表鎖機(jī)制渴语,為了提高插入性能,鎖不是在一個(gè)事務(wù)完成后才釋放昆咽,而是在完成對(duì)自增長(zhǎng)值插入的SQL越劇后立即釋放驾凶。
MySQL 5.1.22開始牙甫,InnoDB提供了一種輕量級(jí)互斥量的增長(zhǎng)實(shí)現(xiàn)機(jī)制,大大提高了自增長(zhǎng)值插入的性能调违。
⑤外鍵和鎖
InnoDB中窟哺,對(duì)于一個(gè)外鍵列搓劫,如果沒有顯式地對(duì)這個(gè)列加索引睦尽,InnoDB自動(dòng)對(duì)其加一個(gè)索引。
對(duì)于外鍵值的插入或更新探熔,首先需要查詢父表中的記錄亩鬼,即select父表殖告。但是對(duì)于父表的select操作,不是使用一致性非鎖定讀的方式雳锋,因?yàn)檫@樣會(huì)發(fā)生數(shù)據(jù)不一致的問題黄绩,因此這時(shí)使用的是select ... lock in share mode方式。
3.鎖的算法
①行鎖的3種算法
InnoDB有3種行鎖的算法:
-
Record Lock:?jiǎn)蝹€(gè)行記錄上的鎖玷过。
總是會(huì)去鎖住索引記錄爽丹,如果InnoDB表在建立時(shí)沒有設(shè)置任何一個(gè)索引,那么這時(shí)InnoDB存儲(chǔ)引擎會(huì)使用隱式的主鍵來進(jìn)行鎖定辛蚊。
Gap Lock:間隙鎖粤蝎,鎖定一個(gè)范圍,但不包含記錄本身袋马。
Next-Key Lock:Gap Lock + Record Lock初澎,鎖定一個(gè)范圍,并且鎖定記錄本身虑凛。
InnoDB在repeatable read事務(wù)隔離級(jí)別下碑宴,采用Next-Key Locking的方式來加鎖,在read committed下桑谍,僅采用Record Lock延柠。
當(dāng)查詢的索引含有唯一屬性時(shí),InnoDB會(huì)對(duì)Next-Key Lock進(jìn)行優(yōu)化锣披,將其降級(jí)為Record Lock贞间。
如:
create table t (a int primary key);
insert into t values (1),(2),(5);
select * from t where a=5 for update;//僅僅鎖定5這個(gè)值,而不是(2,5)這個(gè)范圍雹仿。
create table z (a int, b int, primary key(a), key(b));
insert into z(a,b) values (1,1),(3,1),(5,3),(7,6),(10,8);
select * from z where b=3 for update;//鎖定范圍(1,3)增热、(3,6) (b列)
insert into z(a,b) values (8,3);
若沒有鎖定范圍(3,6),那么用戶可以插入索引b列為3的記錄盅粪,這會(huì)導(dǎo)致會(huì)話A中的用戶再次執(zhí)行同樣查詢時(shí)會(huì)返回不同的記錄,即導(dǎo)致Phantom Problem問題的產(chǎn)生悄蕾。
②解決Phantom Problem(幻像問題)
默認(rèn)的事務(wù)隔離級(jí)別下(repeatable read)下票顾,InnoDB采用Next-Key Locking機(jī)制來避免Phantom Problem(幻像問題)础浮。Oracle數(shù)據(jù)庫(kù)需要在serializable的事務(wù)隔離級(jí)別才能解決Phantom Problem。
Phantom Problem是指:在同一事務(wù)下奠骄,連續(xù)執(zhí)行兩次同樣的SQL語句可能導(dǎo)致不同的結(jié)果豆同,第二次的SQL語句可能會(huì)返回之前不存在的行。
如:select * from t where a>2 for update;//對(duì)(2,+∞)這個(gè)范圍加X鎖含鳞。
4.鎖問題
①臟讀
臟頁(yè):在緩沖池中已經(jīng)被修改的頁(yè)影锈,但是還沒有刷新到磁盤中,即數(shù)據(jù)庫(kù)實(shí)例內(nèi)存中的頁(yè)和磁盤中的頁(yè)的數(shù)據(jù)是不一致的蝉绷。
臟數(shù)據(jù):事務(wù)對(duì)緩沖池中行記錄的修改鸭廷,并且還沒有被提交(commit)。
臟讀:在不同的事務(wù)下熔吗,當(dāng)前事務(wù)可以讀到另外事務(wù)未提交的數(shù)據(jù)辆床,簡(jiǎn)單來說就是可以讀到臟數(shù)據(jù)。
對(duì)于臟頁(yè)的讀取桅狠,是非常正常的讼载。臟數(shù)據(jù)卻截然不同,如果讀到了臟數(shù)據(jù)中跌,即一個(gè)事務(wù)可以讀到另一個(gè)事務(wù)中未提交的數(shù)據(jù)咨堤,則顯然違反了數(shù)據(jù)庫(kù)的隔離性。
臟讀發(fā)生在事務(wù)隔離級(jí)別為 read uncommitted漩符。
②不可重復(fù)讀
不可重復(fù)讀:在一個(gè)事務(wù)內(nèi)多次讀取同一數(shù)據(jù)集合一喘,在這個(gè)事務(wù)還沒結(jié)束時(shí),另外一個(gè)事務(wù)也訪問該同一數(shù)據(jù)集合陨仅,并做了一些DML操作津滞。因此第一個(gè)事務(wù)中兩次讀到的數(shù)據(jù)可能是不一樣的。
不可重復(fù)讀違反了數(shù)據(jù)庫(kù)事務(wù)一致性的要求灼伤。
read committed允許不可重復(fù)讀線線触徐。
InnoDB默認(rèn)事務(wù)隔離級(jí)別read repeatable,采用Next-Key Lock算法狐赡,避免了不可重復(fù)讀的現(xiàn)象撞鹉。
③丟失更新
一個(gè)事務(wù)的更新操作會(huì)被另一個(gè)事務(wù)的更新操作所覆蓋,從而導(dǎo)致數(shù)據(jù)的不一致颖侄。
如:
- 事務(wù)T1將行記錄r更新為v1鸟雏,但是事務(wù)T1未提交。
- 與此同時(shí)览祖,事務(wù)T2將行記錄r更新為v2孝鹊,事務(wù)T2未提交。(實(shí)際數(shù)據(jù)庫(kù)并不能更新)
- 事務(wù)T1提交展蒂。
- 事務(wù)T2提交又活。
但是在當(dāng)前數(shù)據(jù)庫(kù)的任何隔離級(jí)別下苔咪,都不會(huì)導(dǎo)致數(shù)據(jù)庫(kù)理論意義上的丟失更新問題。因?yàn)榧词故莚ead uncommitted的事務(wù)隔離級(jí)別柳骄,對(duì)于行的DML操作团赏,需要對(duì)行貨其他粗粒度級(jí)別的對(duì)象加鎖。因此在步驟2中事務(wù)T2并不能對(duì)行記錄r進(jìn)行更新操作耐薯,其會(huì)被阻塞舔清,直到事務(wù)T1提交。
數(shù)據(jù)庫(kù)本身不會(huì)丟失更新曲初,但是多用戶業(yè)務(wù)邏輯上可能發(fā)生丟失更新:
- 事務(wù)T1查詢一行數(shù)據(jù)体谒,放入本地內(nèi)存,并顯示給一個(gè)終端用戶user1复斥。
- 事務(wù)T2也查詢?cè)撔袛?shù)據(jù)营密,并將獲取到的數(shù)據(jù)顯示給終端用戶user2。
- user1修改這行記錄目锭,更新數(shù)據(jù)庫(kù)并提交评汰。
- user2修改這行記錄,更新數(shù)據(jù)庫(kù)并提交痢虹。
解決方案:
步驟1和2中被去,對(duì)用戶讀取的記錄加上一個(gè)排他X鎖。這樣步驟2必須等待步驟1和步驟3完成奖唯,最后完成步驟4惨缆。
5.阻塞
因?yàn)椴煌i之間的兼容性關(guān)系,在有些時(shí)刻一個(gè)事務(wù)中的鎖需要等待另一個(gè)事務(wù)中的鎖釋放它鎖占用的資源丰捷,這就是阻塞坯墨。
阻塞并不是一件壞事,其是為了確保事務(wù)可以并發(fā)且正常地運(yùn)行病往。
InnoDB中捣染,可以通過參數(shù)來控制等待的時(shí)間(默認(rèn)50秒),也可以通過參數(shù)控制是否在等待超時(shí)時(shí)對(duì)進(jìn)行中的事務(wù)進(jìn)行回滾操作(默認(rèn)不回滾)停巷。
如:
會(huì)話A 開啟事務(wù) select * from t where a<4 for update;//鎖定小于等于4的所有記錄
會(huì)話B 開啟事務(wù) insert into t select 5;//可以插入
會(huì)話B insert into t select 3;//超時(shí)耍攘,拋出異常
會(huì)話B select * from t;//包含5
因?yàn)闀?huì)話B中的事務(wù)雖然拋出了異常,但是既沒有進(jìn)行commit操作畔勤,也沒有進(jìn)行rollback蕾各。這是十分危險(xiǎn)的狀態(tài),因此用戶必須判斷是否需要commit還是rollback庆揪,之后再進(jìn)行下一步的操作式曲。
6.死鎖
①死鎖的概念
死鎖是指兩個(gè)或兩個(gè)以上的事務(wù)在執(zhí)行過程中,因爭(zhēng)奪鎖資源而造成的一種互相等待的現(xiàn)象缸榛。
解決死鎖的方法:
超時(shí)機(jī)制:當(dāng)兩個(gè)事務(wù)互相等待時(shí)吝羞,當(dāng)一個(gè)等待時(shí)間超過設(shè)置的某一閾值時(shí)始鱼,其中一個(gè)事務(wù)進(jìn)行回滾,另一個(gè)等待的事務(wù)就能繼續(xù)進(jìn)行脆贵。
wait-for graph(等待圖):主動(dòng)的死鎖檢測(cè)方式。InnoDB也采用這種方式起暮。
wait-for graph要求數(shù)據(jù)保存以下兩種信息:
- 鎖的信息鏈表
- 事務(wù)等待鏈表
通過上述鏈表可以構(gòu)造出一張圖卖氨,而在這個(gè)圖中若存在回路,就代表存在死鎖负懦。
若存在死鎖筒捺,通常來說InnoDB選擇回滾undo量最小的事務(wù)。
②死鎖的概率
死鎖應(yīng)該非常少發(fā)生纸厉,若經(jīng)常發(fā)生系吭,則系統(tǒng)是不可用的。死鎖的次數(shù)應(yīng)該還少于等待颗品,因?yàn)橹辽傩枰?次等待才會(huì)產(chǎn)生一次死鎖肯尺。
事務(wù)發(fā)生死鎖的概率與一下幾點(diǎn)因素有關(guān):
- 系統(tǒng)中事務(wù)的數(shù)量越多,發(fā)生死鎖的概率越大躯枢。
- 每個(gè)事務(wù)操作的數(shù)量越多则吟,發(fā)生死鎖的概率越大。
- 操作數(shù)據(jù)的集合越小锄蹂,則發(fā)生死鎖的概率越大氓仲。
③死鎖的示例
如果程序是串行的,那么不可能發(fā)生死鎖得糜。死鎖只存在于并發(fā)的情況敬扛。
示例1:
- 事務(wù)A:select * from t where a=1 for update;
- 事務(wù)B:select * from t where a=2 for update;
- 事務(wù)A:select * from t where a=2 for update;//等待
- 事務(wù)B:select * from t where a=1 for update;//死鎖,回滾朝抖,ERROR 1212(40001):Deadlock found when trying to get lock;try restarting transaction
- 事務(wù)A得到記錄為2的這個(gè)資源啥箭。
InnoDB并不會(huì)回滾大本分的錯(cuò)誤異常,但是死鎖除外槽棍。發(fā)現(xiàn)死鎖后捉蚤,InnoDB會(huì)馬上回滾一個(gè)事務(wù)。
示例2:
- 事務(wù)A:select * from t where a=4 for update;//對(duì)記錄4持有了X鎖
- 事務(wù)B:select * from t where a<=4 lock in share mode;//獲取小于4的記錄的鎖炼七,等待記錄4的鎖
- 事務(wù)A:insert into t values (3);//ERROR 1213(40001):Deadlock found when trying to get lock;try restarting transaction
- 事務(wù)B:獲得鎖缆巧,正常運(yùn)行。
7.鎖升級(jí)(Lock Escalation)
鎖升級(jí):指將當(dāng)前鎖的粒度降低豌拙。
InnoDB存儲(chǔ)引擎不存在鎖升級(jí)的問題陕悬。因?yàn)槠洳皇歉鶕?jù)每個(gè)記錄來產(chǎn)生行鎖的,相反按傅,其根據(jù)每個(gè)事務(wù)訪問的每個(gè)頁(yè)對(duì)鎖進(jìn)行管理的捉超,采用的是位圖的方式胧卤。因此不管一個(gè)事務(wù)鎖住頁(yè)中一個(gè)記錄還是多個(gè)記錄,其開銷通常都是一致的拼岳。