MySQL 中關(guān)于鎖的高頻考點(diǎn),拿捏了
小伙伴們大家好,我是小牛肉~ 元旦的第二天,馬上就能放假了,莫名興奮,不過(guò)這個(gè)寒假準(zhǔn)備春招實(shí)習(xí)的小伙伴肯定都要開(kāi)始準(zhǔn)備起來(lái)了,誒,不容易呀~ 準(zhǔn)備春招秋招或者已經(jīng)上岸想要發(fā)內(nèi)推消息的小伙伴們,可以加我微信 jcool1998,我拉你進(jìn)群【互聯(lián)網(wǎng)春招秋招交流群】,大家一起來(lái)交流吐槽消息共享呀~
今天給大家分享一個(gè)很 nice 的牛客上關(guān)于 MySQL 鎖相關(guān)的總結(jié)類文章:
1.什么是鎖,鎖的作用是什么?
回答:鎖是數(shù)據(jù)庫(kù)系統(tǒng)區(qū)別文件系統(tǒng)的一個(gè)關(guān)鍵特性,鎖機(jī)制用于管理對(duì)共享資源的并發(fā)訪問(wèn),保持?jǐn)?shù)據(jù)的完整性和一致性?!菊裕篗ySQL技術(shù)內(nèi)幕InnoDB存儲(chǔ)引擎】
2.數(shù)據(jù)庫(kù)有哪些鎖?lock和latch的區(qū)別
回答:數(shù)據(jù)庫(kù)中有表鎖和行鎖等
lock鎖:鎖的對(duì)象是事務(wù),用于鎖定數(shù)據(jù)庫(kù)中的對(duì)象,如表、頁(yè)、行等,并且lock鎖一般在commit或rollback后釋放,有死鎖機(jī)制。
latch鎖:一般稱為輕量級(jí)鎖,要求鎖定的時(shí)間必須非常短,在InnoDB中又可以分為mutex(互斥量)和rwlock(讀寫(xiě)鎖)。目的是用來(lái)保證并發(fā)線程操作臨界資源的正確性,并且通常沒(méi)有死鎖檢測(cè)的機(jī)制。
3.InnoDB存儲(chǔ)引擎中的鎖都有哪些類型?
回答:可以分為共享鎖、排他鎖、意向鎖、一致性非鎖定讀和一致性鎖定讀。
其中共享鎖和排他鎖均屬于行級(jí)鎖。
共享鎖(S Lock):運(yùn)行事務(wù)讀一行數(shù)據(jù)。
排他鎖(X Lock):允許事務(wù)刪除或更新一行數(shù)據(jù)。
行鎖的三種算法:
Record Lock:?jiǎn)蝹€(gè)行記錄上的鎖
Gap Lock:間隙鎖,鎖定一個(gè)范圍,但不包含記錄本身。
Next-Key Lock:Gap+Record Lock鎖定一個(gè)范圍,并且鎖定記錄本身。
意向鎖屬于表級(jí)別的鎖,又可以分為意向共享鎖(IS Lock)和意向排他鎖(IX Lock)。
意向共享鎖(IS Lock):事務(wù)想要獲得一張表中某幾行的共享鎖。
意向排他鎖(IX Lock):事務(wù)想要獲得一張表中某幾行的排他鎖。
一致性非鎖定讀:指InnoDB存儲(chǔ)引擎通過(guò)多版本控制的方式來(lái)讀取當(dāng)前執(zhí)行時(shí)間數(shù)據(jù)庫(kù)中行的數(shù)據(jù)。如果讀取的行正在執(zhí)行DELETE或UPDATE操作,這時(shí)讀取操作不會(huì)因此等待行上鎖的釋放,相反的,InnoDB存儲(chǔ)引擎會(huì)讀取一個(gè)快照數(shù)據(jù)。
一致性鎖定讀:InnoDB存儲(chǔ)引擎對(duì)于SELECT語(yǔ)句支持兩種一致性鎖定讀的操作:
select ... for update和select ... lock in share mode。
4.什么是一致性非鎖定讀(MVCC)?
什么是MVCC?
MVCC實(shí)現(xiàn)原理【MVCC多版本并發(fā)控制,指的是一種提高并發(fā)的技術(shù)?!?Multi-Version Concurrency Control。最早的數(shù)據(jù)庫(kù)系統(tǒng),只有讀讀之間可以并發(fā),讀寫(xiě),寫(xiě)讀,寫(xiě)寫(xiě)都要阻塞。引入多版本之后,只有寫(xiě)寫(xiě)之間相互阻塞,其他三種操作都可以并行,這樣大幅度提高了InnoDB的并發(fā)度。
MVCC能解決什么問(wèn)題,好處是?
數(shù)據(jù)庫(kù)并發(fā)場(chǎng)景有三種,分別為:讀-讀:不存在任何問(wèn)題,也不需要并發(fā)控制 讀-寫(xiě):有線程安全問(wèn)題,可能會(huì)造成事務(wù)隔離性問(wèn)題,可能遇到臟讀,幻讀,不可重復(fù)讀 寫(xiě)-寫(xiě):有線程安全問(wèn)題,可能會(huì)存在更新丟失問(wèn)題,比如第一類更新丟失,第二類更新丟失
MVCC帶來(lái)的好處是?
MVCC可以為數(shù)據(jù)庫(kù)解決以下問(wèn)題
在并發(fā)讀寫(xiě)數(shù)據(jù)庫(kù)時(shí),可以做到在讀操作時(shí)不用阻塞寫(xiě)操作,寫(xiě)操作也不用阻塞讀操作,提高了數(shù)據(jù)庫(kù)并發(fā)讀寫(xiě)的性能 同時(shí)還可以解決臟讀,幻讀,不可重復(fù)讀等事務(wù)隔離問(wèn)題,但不能解決更新丟失問(wèn)題 MVCC只在讀取已提交和可重復(fù) 讀兩種隔離級(jí)別下有作用
MVCC常見(jiàn)的實(shí)現(xiàn)方式樂(lè)觀鎖和悲觀鎖
MVCC是行級(jí)鎖的變種,很多情況下避免了加鎖操作。
應(yīng)對(duì)高并發(fā)事務(wù), MVCC比單純的加鎖更高效;
InnoDB存儲(chǔ)引擎在數(shù)據(jù)庫(kù)每行數(shù)據(jù)的后面添加了三個(gè)字段, 不是兩個(gè)!!
核心概念【很重要?。?!】
1.Read view一致性視圖【 主要是用來(lái)做可見(jiàn)性判斷的, 比較普遍的解釋便是"本事務(wù)不可見(jiàn)的當(dāng)前其他活躍事務(wù)", 】
2.read view快照的生成時(shí)機(jī), 也非常關(guān)鍵, 正是因?yàn)樯蓵r(shí)機(jī)的不同, 造成了RC,RR兩種隔離級(jí)別的不同可見(jiàn)性;
在innodb中(默認(rèn)repeatable read級(jí)別), 事務(wù)在begin/start transaction之后的第一條select讀操作后, 會(huì)創(chuàng)建一個(gè)快照(read view), 將當(dāng)前系統(tǒng)中活躍的其他事務(wù)記錄記錄起來(lái); 在innodb中(默認(rèn)repeatable committed級(jí)別), 事務(wù)中每條select語(yǔ)句都會(huì)創(chuàng)建一個(gè)快照(read view); 3.undo-log 【回滾日志,通過(guò)undo讀取之前的版本信息,以此實(shí)現(xiàn)非鎖定讀取!】 是MVCC的重要組成部分!
當(dāng)我們對(duì)記錄做了變更操作時(shí)就會(huì)產(chǎn)生undo記錄,Undo記錄默認(rèn)被記錄到系統(tǒng)表空間(ibdata)中,但從5.6開(kāi)始,也可以使用獨(dú)立的Undo 表空間。
Undo記錄中存儲(chǔ)的是老版本數(shù)據(jù),當(dāng)一個(gè)舊的事務(wù)需要讀取數(shù)據(jù)時(shí),為了能讀取到老版本的數(shù)據(jù),需要順著undo鏈找到滿足其可見(jiàn)性的記錄。
另外, 在回滾段中的undo logs分為: insert undo log 和 update undo log insert undo insert undo log : 事務(wù)對(duì)insert新記錄時(shí)產(chǎn)生的undolog, 只在事務(wù)回滾時(shí)需要, 并且在事務(wù)提交后就可以立即丟棄。update undo update undo log : 事務(wù)對(duì)記錄進(jìn)行delete和update操作時(shí)產(chǎn)生的undo log, 不僅在事務(wù)回滾時(shí)需要, 一致性讀也需要,所以不能隨便刪除,只有當(dāng)數(shù)據(jù)庫(kù)所使用的快照中不涉及該日志記錄,對(duì)應(yīng)的回滾日志才會(huì)被purge線程刪除。
4.InnoDB存儲(chǔ)引擎在數(shù)據(jù)庫(kù)每行數(shù)據(jù)的后面添加了三個(gè)字段 分別是事務(wù)ID、回滾指針和
6字節(jié)的DB_ROW_ID字段: 包含一個(gè)隨著新行插入而單調(diào)遞增的行ID, 當(dāng)由innodb自動(dòng)產(chǎn)生聚集索引時(shí),聚集索引會(huì)包括這個(gè)行ID的值,否則這個(gè)行ID不會(huì)出現(xiàn)在任何索引中。
5.可見(jiàn)性比較算法(這里每個(gè)比較算法后面的描述是建立在rr級(jí)別下,rc級(jí)別也是使用該比較算法,此處未做描述)
設(shè)要讀取的行的最后提交事務(wù)id(即當(dāng)前數(shù)據(jù)行的穩(wěn)定事務(wù)id)為 trx_id_current
當(dāng)前新開(kāi)事務(wù)id為 new_id
當(dāng)前新開(kāi)事務(wù)創(chuàng)建的快照read view 中最早的事務(wù)id為up_limit_id, 最遲的事務(wù)id為low_limit_id(注意這個(gè)low_limit_id=未開(kāi)啟的事務(wù)id=當(dāng)前最大事務(wù)id+1)
比較:
trx_id_current < up_limit_id, 這種情況比較好理解, 表示, 新事務(wù)在讀取該行記錄時(shí), 該行記錄的穩(wěn)定事務(wù)ID是小于, 系統(tǒng)當(dāng)前所有活躍的事務(wù), 所以當(dāng)前行穩(wěn)定數(shù)據(jù)對(duì)新事務(wù)可見(jiàn), 跳到步驟5. trx_id_current >= trx_id_last, 這種情況也比較好理解, 表示, 該行記錄的穩(wěn)定事務(wù)id是在本次新事務(wù)創(chuàng)建之后才開(kāi)啟的,但是卻在本次新事務(wù)執(zhí)行第二個(gè)select前就commit了,所以該行記錄的當(dāng)前值不可見(jiàn), 跳到步驟4 trx_id_current <= trx_id_current <= trx_id_last, 表示: 該行記錄所在事務(wù)在本次新事務(wù)創(chuàng)建的時(shí)候處于活動(dòng)狀態(tài),從up_limit_id到low_limit_id進(jìn)行遍歷,如果trx_id_current等于他們之中的某個(gè)事務(wù)id的話,那么不可見(jiàn),調(diào)到步驟4,否則表示可見(jiàn)。 從該行記錄的 DB_ROLL_PTR 指針?biāo)赶虻幕貪L段中取出最新的undo-log的版本號(hào), 將它賦值該 trx_id_current,然后跳到步驟1重新開(kāi)始判斷。 將該可見(jiàn)行的值返回。
5.鎖可能會(huì)帶來(lái)什么問(wèn)題?
回答:通過(guò)鎖機(jī)制實(shí)現(xiàn)了事務(wù)的隔離性,使得事務(wù)可以并發(fā)的工作,但同時(shí)也會(huì)有一些潛在的問(wèn)題。鎖會(huì)帶來(lái)如下問(wèn)題:臟讀、不可重復(fù)度、丟失修改和幻讀。
臟讀(Dirty read): 當(dāng)一個(gè)事務(wù)正在訪問(wèn)數(shù)據(jù)并且對(duì)數(shù)據(jù)進(jìn)行了修改,而這種修改還沒(méi)有提交到數(shù)據(jù)庫(kù)中,這時(shí)另外一個(gè)事務(wù)也訪問(wèn)了這個(gè)數(shù)據(jù),然后使用了這個(gè)數(shù)據(jù)。因?yàn)檫@個(gè)數(shù)據(jù)是還沒(méi)有提交的數(shù)據(jù),那么另外一個(gè)事務(wù)讀到的這個(gè)數(shù)據(jù)是“臟數(shù)據(jù)”,依據(jù)“臟數(shù)據(jù)”所做的操作可能是不正確的。 丟失修改(Lost to modify): 指在一個(gè)事務(wù)讀取一個(gè)數(shù)據(jù)時(shí),另外一個(gè)事務(wù)也訪問(wèn)了該數(shù)據(jù),那么在第一個(gè)事務(wù)中修改了這個(gè)數(shù)據(jù)后,第二個(gè)事務(wù)也修改了這個(gè)數(shù)據(jù)。這樣第一個(gè)事務(wù)內(nèi)的修改結(jié)果就被丟失,因此稱為丟失修改。例如:事務(wù)1讀取某表中的數(shù)據(jù)A=20,事務(wù)2也讀取A=20,事務(wù)1修改A=A-1,事務(wù)2也修改A=A-1,最終結(jié)果A=19,事務(wù)1的修改被丟失。 不可重復(fù)讀(Unrepeatableread): 指在一個(gè)事務(wù)內(nèi)多次讀同一數(shù)據(jù)。在這個(gè)事務(wù)還沒(méi)有結(jié)束時(shí),另一個(gè)事務(wù)也訪問(wèn)該數(shù)據(jù)。那么,在第一個(gè)事務(wù)中的兩次讀數(shù)據(jù)之間,由于第二個(gè)事務(wù)的修改導(dǎo)致第一個(gè)事務(wù)兩次讀取的數(shù)據(jù)可能不太一樣。這就發(fā)生了在一個(gè)事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的情況,因此稱為不可重復(fù)讀。 幻讀(Phantom read): 幻讀與不可重復(fù)讀類似。它發(fā)生在一個(gè)事務(wù)(T1)讀取了幾行數(shù)據(jù),接著另一個(gè)并發(fā)事務(wù)(T2)插入了一些數(shù)據(jù)時(shí)。在隨后的查詢中,第一個(gè)事務(wù)(T1)就會(huì)發(fā)現(xiàn)多了一些原本不存在的記錄,就好像發(fā)生了幻覺(jué)一樣,所以稱為幻讀。
不可重復(fù)讀和幻讀區(qū)別:
不可重復(fù)讀的重點(diǎn)是修改比如多次讀取一條記錄發(fā)現(xiàn)其中某些列的值被修改,幻讀的重點(diǎn)在于新增或者刪除比如多次讀取一條記錄發(fā)現(xiàn)記錄增多或減少了?!菊訫ySQL技術(shù)內(nèi)幕:InnoDB存儲(chǔ)引擎可以使用Next-Key Locking機(jī)制來(lái)避免Phantom Problem問(wèn)題】
6.數(shù)據(jù)庫(kù)中的死鎖概念你知道嗎?
回答:死鎖是指兩個(gè)或兩個(gè)以上的事務(wù)在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象。
解決死鎖的辦法:一種是超時(shí)回滾,一種是采用死鎖檢測(cè)機(jī)制(wait-for graph等待圖)
如果面試官讓你舉例子,可以舉例下面的例子:

在 MySQL 中,gap lock 默認(rèn)是開(kāi)啟的,即innodb_locks_unsafe_for_binlog 參數(shù)值是disable 的,且 MySQL 中默認(rèn)的是 RR 事務(wù)隔離級(jí)別。
當(dāng)我們執(zhí)行以下查詢 SQL 時(shí),由于 order_no 列為非唯一索引,此時(shí)又是 RR 事務(wù)隔離級(jí)別,所以 SELECT 的加鎖類型為 gap lock,這里的 gap 范圍是 (4,+∞)。

執(zhí)行查詢 SQL 語(yǔ)句獲取的 gap lock 并不會(huì)導(dǎo)致阻塞,而當(dāng)我們執(zhí)行以下插入 SQL 時(shí),會(huì)在插入間隙上再次獲取插入意向鎖。插入意向鎖其實(shí)也是一種 gap 鎖,它與 gap lock 是沖突的,所以當(dāng)其它事務(wù)持有該間隙的 gap lock 時(shí),需要等待其它事務(wù)釋放 gap lock 之后,才能獲取到插入意向鎖。
以上事務(wù) A 和事務(wù) B 都持有間隙 (4,+∞)的 gap 鎖,而接下來(lái)的插入操作為了獲取到插入意向鎖,都在等待對(duì)方事務(wù)的 gap 鎖釋放,于是就造成了循環(huán)等待,導(dǎo)致死鎖。

最后送上鎖之間的兼容性表格:

