1. 程式人生 > 程式設計 >面試必問的資料庫中的鎖

面試必問的資料庫中的鎖

前言

上一篇文章中說完了MVCC,我們知道資料庫通過這種類似快照鏈的形式實現了事物的併發,MVCC解決了大部分場景下的事務之間的讀寫併發,讀歷史快照,寫當前版本,寫操作之間是會加鎖的,不然可能造成髒寫; 同時,有的場景下,比如銀行存錢時,讀出賬戶餘額後,再進行金額的新增,同時讀出餘額後,不希望別的賬戶再訪問餘額,即此時的讀寫操作之間也是需要加鎖的;至於讀讀操作,對資料庫不會有任何改變,自然不需要加鎖。

行鎖

前言部分,我們引出了鎖。讀寫和寫寫之間可能都需要加鎖,但是讀讀之間是不需要的。 有沒有什麼想法,我們前幾天才看了 ReentrantReadWriteLock 的實現,資料庫其實跟它差不了太多。

在資料庫中,讀操作需要獲取共享鎖(Shared Locks),一般簡稱為S鎖;寫操作需要獲取獨佔鎖(Exclusive Locks),一般簡稱為X鎖。

而且鎖之間的互斥關係跟 ReentrantReadWriteLock 也可以說基本一致,只是這裡不存在重入次數,將執行緒的概念換成了事務。

  • 讀取記錄時要獲取記錄的S鎖,修改記錄時要獲取記錄的X鎖。

  • S鎖可以由多個事務共同持有,X鎖只能單個事務持有.

  • 事務1持有一條記錄的S鎖,此時事務2想要持有該記錄的X鎖的話會被阻塞,直到事務1放棄S鎖

  • 事務1持有一條記錄的S鎖時,此時別的事務不管是S鎖還是X鎖都不可以獲得

相容性 X S
X 不相容 不相容
S 不相容 相容

鎖定讀

上邊介紹完了鎖,我們其實可以發現如果按上邊所說,仍然不能解決銀行存錢場景中的,事務1獲取S鎖讀取了資料,此時事務2仍然可以獲取事務的S鎖繼續讀取,那麼,我們就在讀取記錄時獲取該記錄的X鎖。上述兩種不同的讀取加鎖方式的實現分別如下

SELECT ... LOCK IN SHARE MODE;
複製程式碼

這是讀取時獲取S鎖

SELECT ... FOR UPDATE
複製程式碼

這樣讀取後會為讀取到的記錄加X鎖

寫操作

DELETE

先獲取指定記錄的X鎖,再進行刪除標記(應該瞭解不會刪除記錄而只是打標記吧~

UPDATE

更新如果更新了記錄的鍵值對需要先獲取X鎖刪掉記錄再進行Insert新紀錄;沒變鍵值對的話直接獲取X鎖進行原地修改

Insert

隱式鎖,既不是S鎖也不是X鎖。

表鎖

上邊說了針對記錄的行鎖,同樣還有表鎖,其實相對於行鎖,無非就是作用範圍變大了,由單條記錄變為了整個表,除此之外的相容性規則完全一樣。不過注意表鎖和行鎖間的互斥,比如一個事務獲得整個表的S鎖,別的事務既可以獲得這個表的S鎖,也可以獲得表中某條記錄的S鎖,至於S鎖,就別想了,我們說了,規則跟上邊一樣,不再贅述。

表鎖種需要注意的只有一個意向鎖,即如果我們想給表加X鎖,此時表任意一條記錄都能有S鎖或者X鎖,如果我們想給表加S鎖,表中任意一條記錄都不能有X鎖。那麼,問題來了,我們怎麼直到表中任意一條記錄有沒有鎖呢?

不可能遍歷吧?當然不可能,實際上是當事務準備在某條記錄加X鎖時,就會在表上加一個IX鎖;當事務準備在某條記錄加S鎖時,就會在表上加一個IS鎖.這樣當別的事務來,一看錶有IX或者IS就知道這個表中的記錄有沒有X鎖或者S鎖了。IX和IS只是代表表中有記錄持有X鎖或者S鎖,所以他們倆並不互斥,當然了IX和IX也不互斥。

說到表鎖,不得不提下MyISAM、MEMORY這些引擎,他們都採用表鎖,而且不支援事務,是基於會話的,這種大粒度的鎖效率是非常慢的,所以這些存貯引擎一般只適用於只讀的場景中

InnoDb中的行級鎖

Record Locks

記錄鎖,其實就是S鎖和X鎖

Gap Locks

解決幻讀就在於此。這個鎖可以鎖住某條記錄和它前邊記錄之間的間隙,使得該間隙不能插入記錄。

Next-Key Locks

不翻譯了就,作用是鎖住指定記錄及其前邊的間隙,即前兩種鎖的合體

Insert Intention Locks

插入意向鎖,很無聊的一個鎖。當事務被間隙鎖阻塞不能成功插入時,會在該間隙生成一個插入意向鎖,當可以插入時便獲得插入意向鎖

隱式鎖

這個挺有意思的。試想這麼一個場景,我們剛插入一個記錄,此時別的事務就過來要這個記錄的S鎖進行讀取或X鎖進行修改,很明顯這會造成髒讀或者髒寫問題。隱式鎖由此而來。

對於聚簇索引記錄,有一個隱藏列trx_id,記錄著最後改動這條記錄的事務id(MVCC才說過的),很自然的,新插入的記錄的trx_id就是他自己的事務id,如果其他事務此時想獲得該記錄的S鎖或者X鎖時,會檢查trx_id代表的事務是否是當前活躍事務,如果是的話,幫助該事務建立一個X鎖(也就是為當前事務建立⼀個鎖結構,is_waiting屬性是false),然後自己進入等待狀態(也就是為⾃⼰也創 建⼀個鎖結構,is_waiting屬性是true)。

對於二級索引,沒有trx_id屬性,但是在⼆級索引頁面的Page Header部分有⼀ 個PAGE_MAX_TRX_ID屬性,該屬性代表對該⻚⾯做改動 的最大的事務id,如果PAGE_MAX_TRX_ID屬性值⼩於 當前最小的活躍事務id,那麼說明對該⻚⾯做修改的事 務都已經提交了,否則就需要在⻚⾯中定位到對應的二級 索引記錄,然後回表找到它對應的聚簇索引記錄,然後再重複情景聚簇索引的做法。

事務id相當於加了⼀個隱式鎖。別的事務在對這條記錄加 S鎖或者X鎖時,由於隱式鎖的存在,會先幫助當前事務生成一 個鎖結構,然後⾃⼰再⽣成⼀個鎖結構後進⼊等待狀態

InnoDb中的表鎖

IS和IX以及S和X前邊說過。

資料庫中我們常用的一個主鍵自增,其實就是表鎖的一個體現。 執行每條插入語句時都會在表上加一個AUTO-INC鎖,然後為每條待插⼊記錄的 AUTO_INCREMENT修飾的列分配遞增的值,在該語句執 ⾏結束後,再把AUTO-INC鎖釋放掉。這樣⼀個事務在持 有AUTO-INC鎖的過程中,其他事務的插⼊語句都要被阻 塞,可以保證⼀個語句中分配的遞增值是連續的。 該鎖尤其適用於無法確定插入記錄的條數的插入

輕量級鎖

在為插⼊語句⽣ 成AUTO_INCREMENT修飾的列的值時獲取⼀下這個輕量 級鎖,然後⽣成本次插⼊語句需要⽤到的 AUTO_INCREMENT列的值之後,就把該輕量級鎖釋放 掉,並不需要等到整個插⼊語句執⾏完才釋放鎖。 即先通過輕量級鎖對AUTO_INCREMENT修飾的列進⾏賦值。 該鎖適用於確定要插入的記錄條數

總結

其實很多東西都是相通的,尤其是我們前邊說過類似共享鎖和獨佔鎖的概念,這裡就很好理解~其他的,也沒什麼好說的,無非就是鎖的作用範圍和時機的一些問題,多看幾遍自然就熟了~