mysql學(xué)習(xí)筆記(三) 鎖

1. 行鎖

InnoDB存儲(chǔ)引擎實(shí)現(xiàn)了如下兩種標(biāo)準(zhǔn)的行級(jí)鎖:

?共享鎖(S Lock),允許事務(wù)讀一行數(shù)據(jù)。

?排他鎖(X Lock),允許事務(wù)刪除或更新一行數(shù)據(jù)。

如果一個(gè)事務(wù)T1已經(jīng)獲得了行r的共享鎖,那么另外的事務(wù)T2可以立即獲得行r的共享鎖,因?yàn)樽x取并沒(méi)有改變行r的數(shù)據(jù),稱這種情況為鎖兼容(Lock Compatible)。但若有其他的事務(wù)T3想獲得行r的排他鎖,則其必須等待事務(wù)T1、T2釋放行r上的共享鎖——這種情況稱為鎖不兼容。下表顯示了共享鎖和排他鎖的兼容性。

屏幕快照 2019-05-29 下午4.14.48.png

對(duì)于insert、update、delete,InnoDB會(huì)自動(dòng)給涉及的數(shù)據(jù)加排他鎖(X);對(duì)于一般的Select語(yǔ)句,InnoDB不會(huì)加任何鎖,事務(wù)可以通過(guò)以下語(yǔ)句給顯示加共享鎖或排他鎖。

1.1 共享鎖

select * from table_name where .....lock in share mode`

事務(wù)1: select * from table_1 where id=1 lock in share mode;

事務(wù)2: select * from table_1 where id=1 lock in share mode;

事務(wù)1: update table_1 set age=10 where id=1;
事務(wù)1: 事務(wù)1更新時(shí)發(fā)現(xiàn)此行鎖被其他事務(wù)享用,等待

事務(wù)2: update table_1 set age=12 where id=1;
事務(wù)2: 事務(wù)2更新時(shí)發(fā)現(xiàn)此行鎖被其他事務(wù)享用,也等待,導(dǎo)致死鎖

1.2 排他鎖

select * from table_name where .....for update

事務(wù)1: select * from table_1 where id=1 for update;

事務(wù)2: select * from table_1 where id=1 for update;
事務(wù)2: 等待...

事務(wù)1: update table_1 set age=10 where id=1;
事務(wù)1: 更新完后釋放鎖

事務(wù)2: 獲得鎖后,得到其他事務(wù)提交的記錄

1.3 行鎖的特性

  • innodb 的行鎖是在有索引的情況下,沒(méi)有索引的表是鎖定全表的。下面是一個(gè)示例,其中id是主鍵:
id name
1 1
2 2
3 3

事務(wù)1update第一條id=1的數(shù)據(jù),事務(wù)不提交;事務(wù)2接著update第二條id=2的數(shù)據(jù)的時(shí)候等待,原因是id沒(méi)有加上索引,導(dǎo)致事務(wù)1鎖的是表鎖而不是行鎖。

2. 意向鎖

InnoDB存儲(chǔ)引擎支持多粒度(granular)鎖定,這種鎖定允許事務(wù)在行級(jí)上的鎖和表級(jí)上的鎖同時(shí)存在。為了支持在不同粒度上進(jìn)行加鎖操作,InnoDB存儲(chǔ)引擎支持一種額外的鎖方式,稱之為意向鎖(Intention Lock)。意向鎖是將鎖定的對(duì)象分為多個(gè)層次,意向鎖意味著事務(wù)希望在更細(xì)粒度(fine granularity)上進(jìn)行加鎖,如圖所示。

屏幕快照 2019-05-29 下午4.22.17.png

若將上鎖的對(duì)象看成一棵樹,那么對(duì)最下層的對(duì)象上鎖,也就是對(duì)最細(xì)粒度的對(duì)象進(jìn)行上鎖,那么首先需要對(duì)粗粒度的對(duì)象上鎖。例如圖6-3,如果需要對(duì)頁(yè)上的記錄r進(jìn)行上X鎖,那么分別需要對(duì)數(shù)據(jù)庫(kù)A、表、頁(yè)上意向鎖IX,最后對(duì)記錄r上X鎖。若其中任何一個(gè)部分導(dǎo)致等待,那么該操作需要等待粗粒度鎖的完成。舉例來(lái)說(shuō),在對(duì)記錄r加X鎖之前,已經(jīng)有事務(wù)對(duì)表1進(jìn)行了S表鎖,那么表1上已存在S鎖,之后事務(wù)需要對(duì)記錄r在表1上加上IX,由于不兼容,所以該事務(wù)需要等待表鎖操作的完成。

InnoDB存儲(chǔ)引擎支持意向鎖設(shè)計(jì)比較簡(jiǎn)練,其意向鎖即為表級(jí)別的鎖。設(shè)計(jì)目的主要是為了在一個(gè)事務(wù)中揭示下一行將被請(qǐng)求的鎖類型。其支持兩種意向鎖:

1)意向共享鎖(IS Lock),事務(wù)想要獲得一張表中某幾行的共享鎖

2)意向排他鎖(IX Lock),事務(wù)想要獲得一張表中某幾行的排他鎖

由于InnoDB存儲(chǔ)引擎支持的是行級(jí)別的鎖,因此意向鎖其實(shí)不會(huì)阻塞除全表掃以外的任何請(qǐng)求。故表級(jí)意向鎖與行級(jí)鎖的兼容性如表所示。

屏幕快照 2019-05-29 下午4.23.30.png

3.快照讀

快照讀又稱一致性非鎖定讀。即讀取到的數(shù)據(jù)始終是快照數(shù)據(jù),而不是最新數(shù)據(jù)。

一致性非鎖定讀是指InnoDB存儲(chǔ)引擎通過(guò)多版本并發(fā)控制技術(shù)來(lái)讀取當(dāng)前數(shù)據(jù)庫(kù)的數(shù)據(jù)。如果當(dāng)前讀取的行正在執(zhí)行delete或者update操作,這時(shí)讀取操作不會(huì)等行鎖的釋放,而是去讀取行的快照數(shù)據(jù)。
非鎖定一致性讀.png

快照數(shù)據(jù)是指改行之前版本的數(shù)據(jù),該實(shí)現(xiàn)是通過(guò)undo段來(lái)實(shí)現(xiàn)的,而undo段用來(lái)在事務(wù)中保存回滾數(shù)據(jù),因此使用快照沒(méi)有增加額外的開銷。 這是InnoDB存儲(chǔ)引擎的默認(rèn)讀取方式,即默認(rèn)的事務(wù)隔離級(jí)別為REPEATABLE READ下的讀取方式。由于讀到的不是最新數(shù)據(jù),某些情況下可能會(huì)有問(wèn)題。

3.1 注意

  • 不同的事務(wù)隔離級(jí)別下讀取的方式不同,并不是每個(gè)事務(wù)隔離級(jí)別下都是采用非鎖定的一致性讀。四種隔離級(jí)別中,READ COMMITTED和REPEATABLE READ這兩種隔離級(jí)別使用非鎖定的一致性讀。

  • 不同的事務(wù)即使都使用非鎖定的一致性讀,但是對(duì)于快照數(shù)據(jù)的定義也各不相同

    • READ COMMITTED級(jí)別下非鎖定讀總是讀取鎖定行的最新一份快照數(shù)據(jù),即事務(wù)開始后不止一個(gè)快照,有很多版本的數(shù)據(jù),取的是最新的數(shù)據(jù)。

    • REPEATABLE READ級(jí)別下非鎖定讀總是讀取事務(wù)開始后第一次執(zhí)行select操作的數(shù)據(jù)版本(重點(diǎn)在select操作在哪里調(diào)用,取的就是哪個(gè)時(shí)間的數(shù)據(jù))。

3.2 例子

事務(wù)A 事務(wù)B
select * from table where id='1';
. update table set id =3 where id=1;
select * from table where id='1';
. commit;
select * from table where id='1';
  • 上述例子中事務(wù)B update以后,事務(wù)A第一次select的時(shí)候RC級(jí)別和RR級(jí)別獲取的結(jié)果都是id=1的那一條數(shù)據(jù);第二次select的時(shí)候,由于事務(wù)B已經(jīng)提交,RC級(jí)別select的結(jié)果就是事務(wù)Bcommit后更新的最新數(shù)據(jù):id=3,而RR級(jí)別讀取的是事務(wù)A開始時(shí)的數(shù)據(jù),id=1。

4. 當(dāng)前讀

當(dāng)前讀又稱一致性鎖定讀,即不管什么時(shí)候讀,讀到的數(shù)據(jù)都是最新的數(shù)據(jù),而不是事務(wù)開始時(shí)的快照數(shù)據(jù)。

默認(rèn)配置下事務(wù)的隔離級(jí)別為REPEATABLE READ,select操作為一致性非鎖定讀,但某些情況下需要對(duì)數(shù)據(jù)庫(kù)讀取操作進(jìn)行加鎖保證數(shù)據(jù)的一致性。select 有兩種一致的鎖定讀:

  • select ... for update

  • select ... lock in share mode

5. 自增長(zhǎng)與鎖

InnoDB存儲(chǔ)引擎內(nèi)部對(duì)每個(gè)含有自增長(zhǎng)列的表有一個(gè)自增長(zhǎng)計(jì)數(shù)器,當(dāng)進(jìn)行insert操作時(shí),首先獲取計(jì)數(shù)器的最大值,加1后進(jìn)行insert操作。這個(gè)操作會(huì)加一個(gè)特殊的表鎖,AUTO-INC LOCK。這個(gè)鎖并不是在事務(wù)提交后才釋放,而是在insert語(yǔ)句執(zhí)行完后釋放。

5.1 缺點(diǎn)

雖然是insert后就釋放鎖,不是事務(wù)提交后才釋放,但是必須等前一個(gè)insert的完成才能進(jìn)行下一次insert,性能較差。

5.2 改進(jìn)

InnoDB存儲(chǔ)引擎中提供了一種輕量級(jí)互斥量的自增長(zhǎng)實(shí)現(xiàn)機(jī)制,大大提高了自增長(zhǎng)值插入的性能。

6. 外鍵與鎖

在對(duì)外鍵值進(jìn)行update和insert操作時(shí)首先需要查詢父表的記錄,即select父表,這個(gè)select操作不是使用一致性非鎖定讀,因?yàn)闀?huì)發(fā)生數(shù)據(jù)不一致的問(wèn)題,為此需要使用一致性鎖定讀,這時(shí)使用的select ... lock in share mode方式。當(dāng)父表對(duì)應(yīng)記錄加X鎖后,子表的操作將會(huì)阻塞。

7. 鎖的算法

InnoDB存儲(chǔ)引擎有三種行鎖的算法

  • Record Lock:單行記錄上鎖

  • Gap Lock:間隙鎖,鎖定一個(gè)范圍,但不包含記錄本身

  • Next-Key Lock:相當(dāng)于Record Lock+Gap Lock,鎖定一個(gè)范圍,并且鎖定記錄本身

7.1 示例

如果一個(gè)索引有10,11,13,20這四個(gè)值,那么該索引可能被Next-Key Lock分為如下幾個(gè)區(qū)間:

(-∞,10],
(10,11],
(11,13],
(13,20],
(20,+∞)

除了Next-Key Lock外,還有previous-key lock技術(shù),可鎖定的范圍為:

(-∞,10),
[10,11),
[11,13),
[13,20),
[20,+∞)

然而,當(dāng)索引為唯一索引時(shí),InnoDB存儲(chǔ)引擎會(huì)對(duì)Next-key Lock進(jìn)行優(yōu)化,將其降級(jí)為Record Lock,即僅鎖住索引本身,而不是范圍。示例如下:

事務(wù)A 事務(wù)B
Begin
select * from t where a=5 for update;
Begin
Insert into t select 4;
Commit;

該示例中a字段先設(shè)置為唯一索引,當(dāng)事務(wù)a執(zhí)行for update后,事務(wù)b插入4的時(shí)候,由于鎖定的是5這一行,而不是范圍,所以事務(wù)B不會(huì)阻塞。

問(wèn)題一:什么時(shí)候會(huì)用到next-key鎖?

  • 在出現(xiàn)幻讀的時(shí)候,為解決該問(wèn)題,需要鎖定一個(gè)范圍,而不是固定一行,這個(gè)時(shí)候就會(huì)用到next-key鎖了。
  • mysql四個(gè)事務(wù)隔離級(jí)別下,READ UNCOMMITTED和READ COMMITTED這兩個(gè)事務(wù)隔離級(jí)別是不支持next-key鎖的;REPEATABLE READ 和 SERIALZABLE這兩種事務(wù)隔離級(jí)別是支持next-key鎖的。

問(wèn)題二:Next-key鎖只存在于查詢條件為區(qū)間的時(shí)候嗎?

  • 答案不是的,如果查詢條件指定了某一行,但是查詢條件的字段是非唯一索引或不是索引的當(dāng)前讀下,也會(huì)使用next-key鎖。

8. 鎖問(wèn)題

8.1 臟讀

  • 臟讀(Dirty Reads):一個(gè)事務(wù)正在對(duì)一條記錄做修改,在這個(gè)事務(wù)并提交前,這條記錄的數(shù)據(jù)就處于不一致狀態(tài);這時(shí),另一個(gè)事務(wù)也來(lái)讀取同一條記錄,如果不加控制,第二個(gè)事務(wù)讀取了這些“臟”的數(shù)據(jù),并據(jù)此做進(jìn)一步的處理,就會(huì)產(chǎn)生未提交的數(shù)據(jù)依賴關(guān)系。這種現(xiàn)象被形象地叫做“臟讀”。
  • 當(dāng)事務(wù)的隔離級(jí)別為READ UNCOMMITTED會(huì)產(chǎn)生臟讀現(xiàn)象,其他隔離級(jí)別則不會(huì)。

8.2 不可重復(fù)讀

  • 不可重復(fù)讀是指在一個(gè)事務(wù)內(nèi)多次讀取同一數(shù)據(jù)集合。在這個(gè)事務(wù)還沒(méi)有結(jié)束時(shí),另外一個(gè)事務(wù)也訪問(wèn)該同一數(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ù)庫(kù)事務(wù)一致性的要求。
  • 事務(wù)隔離級(jí)別為READ UNCOMMITTED和READ COMMITTED會(huì)產(chǎn)生不可重復(fù)讀現(xiàn)象,為解決此問(wèn)題,可將事務(wù)隔離級(jí)別設(shè)置為REPEATABLE READ,該級(jí)別默認(rèn)實(shí)現(xiàn)為Next-key Lock鎖技術(shù),避免了不可重復(fù)讀的現(xiàn)象。當(dāng)然光設(shè)置事務(wù)隔離級(jí)別為REPEATABLE READ還不行,還得使用select … for update或者select … lock in share mode語(yǔ)句才行。

8.3 幻讀

  • 幻讀指的是一個(gè)事務(wù)按相同的查詢條件重新讀取以前檢索過(guò)的數(shù)據(jù),卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù),這種現(xiàn)象就稱為“幻讀”。
  • 幻讀和不可重復(fù)讀的區(qū)別在于不可重復(fù)讀注重的是同一條記錄前后多次讀取出現(xiàn)結(jié)果不一致的問(wèn)題,而幻讀注重的是范圍內(nèi)增加了以前沒(méi)有的新數(shù)據(jù)而導(dǎo)致總的記錄數(shù)發(fā)生變化的問(wèn)題。

8.4 丟失更新

當(dāng)兩個(gè)或多個(gè)事務(wù)選擇同一行,然后基于最初選定的值更新該行時(shí),由于每個(gè)事務(wù)都不知道其他事務(wù)的存在,就會(huì)發(fā)生丟失更新問(wèn)題——最后的更新覆蓋了其他事務(wù)所做的更新。

比如:

事務(wù)A 事務(wù)B
Begin Begin
select * from t where a=5;
... select * from t where a= 5;
update t set b='1' where a=5; ...
update t set b='2' where a=5;

上述兩個(gè)事務(wù)互相不知道對(duì)方也在同時(shí)執(zhí)行,并且都先進(jìn)行了select操作,后進(jìn)行了update操作,但是最后事務(wù)B的結(jié)果覆蓋了事務(wù)A的結(jié)果,即丟失了A更新的結(jié)果。為避免這個(gè)問(wèn)題應(yīng)該在事務(wù)隔離級(jí)別采用REPEATABLE READ的前提上,select 查詢語(yǔ)句都加上for update就可以了。

9. 阻塞

因?yàn)椴煌i之間的兼容性關(guān)系,在有些時(shí)刻一個(gè)事務(wù)中的鎖需要等待另一個(gè)事務(wù)中的鎖釋放它所占用的資源,這就是阻塞。阻塞并不是一件壞事,其是為了確保事務(wù)可以并發(fā)且正常地運(yùn)行。

在InnoDB存儲(chǔ)引擎中:

  • 參數(shù)innodb_lock_wait_timeout用來(lái)控制等待的時(shí)間(默認(rèn)是50秒)
  • innodb_rollback_on_timeout用來(lái)設(shè)定是否在等待超時(shí)時(shí)對(duì)進(jìn)行中的事務(wù)進(jìn)行回滾操作(默認(rèn)是OFF,代表不回滾)。

參數(shù)innodb_lock_wait_timeout是動(dòng)態(tài)的,可以在MySQL數(shù)據(jù)庫(kù)運(yùn)行時(shí)進(jìn)行調(diào)整,而innodb_rollback_on_timeout是靜態(tài)的,不可在啟動(dòng)時(shí)進(jìn)行修改。

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

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

  • 昨天下午見識(shí)到了很多很厲害的人,表示又新開了眼界,被打擊的體無(wú)完膚,壓力山大。怎么說(shuō),也算是有所收獲。我清楚自己的...
    哈哈你好呀閱讀 72評(píng)論 0 1
  • 都有理想 何曾實(shí)現(xiàn) 現(xiàn)實(shí)像 刻刀 誰(shuí)的模樣未曾改變 接受現(xiàn)實(shí) 你才是贏家 心中有夢(mèng) 花開不敗
    睡眠列車_dd77閱讀 220評(píng)論 0 0
  • 周末兩天的陰雨和霧霾,終于在今日一掃而空~ 不過(guò)今兒輪到胖紙那組值日領(lǐng)料,隔著好幾十公里,他都攬了這個(gè)活,因?yàn)榻M里...
    sunvv1008閱讀 343評(píng)論 0 0

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