1. 程式人生 > 程式設計 >InnoDB事務模型和鎖定

InnoDB事務模型和鎖定

閱讀前警⚠️告

閱讀本文需要對MySQL事務,隔離級別有些概念上的瞭解,關於這些概念你還不夠瞭解的話,請你立即停止閱讀!接下來的內容會讓你頭疼欲裂

讀寫鎖

在處理併發讀或者寫時,可以通過實現一個由兩種型別的鎖組成的鎖系統來解決併發問題。這兩種型別的鎖通常被稱為共享鎖(shared lock)和排他鎖(exclusive lock),也叫讀鎖(read lock)和寫鎖(write lock)

這裡先不討論鎖的具體實現,描述一下鎖的概念如下:讀鎖是共享的,或者說是相互不阻塞的。多個客戶在同一時刻可以同時讀取同一個資源,而互不幹擾。寫鎖則是排他的,也就是說一個寫鎖會阻塞其他的寫鎖和讀鎖,這是出於安全策略的考慮,只有這樣,才能確保在給定的時間裡,只有一個使用者能執行寫入,並防止其他使用者讀取正在寫入的同一資源

簡而言之,允許同時讀,但是不允許邊讀邊寫,也不允許同時寫

鎖粒度

表鎖

表鎖是MySQL中最基本的鎖策略,並且是開銷最小的策略。 它會鎖定整張表。一個使用者在對錶進行寫操作(插入、刪除、更新等)前,需要先獲得寫鎖,這會阻塞其他使用者對該表的所有讀寫操作。 只有沒有寫鎖時,其他讀取的使用者才能獲得讀鎖,讀鎖之間是不相互阻塞的

在特定的場景中,表鎖也可能有良好的效能。例如,READ LOCAL表鎖支援某些型別的併發寫操作

儘管儲存引擎可以管理自己的鎖,MySQL本身還是會使用各種有效的表鎖來實現不同的目的。例如,伺服器會為諸如ALTER TABLE之類的語句使用表鎖,而忽略儲存引擎的鎖機制

行鎖

行級鎖可以最大程度地支援併發處理(同時也帶來了最大的鎖開銷)。眾所周知,在InnoDB和XtraDB,以及其他一些儲存引擎中實現了行級鎖。行級鎖只在儲存引擎層實現,而MySQL伺服器層有實現

說在前面,關於意向鎖

提出意向鎖概念之前,先試想一個場景。當一個事務1已經對錶A的某一行加了讀鎖,此時事務2想在表A上加寫鎖,那麼這種情況應該允許嗎?

當然不應該允許,事務2在表A上加寫鎖,那麼是不是意味著事務2可以對錶A的任意行進行修改?那麼事務1加的讀鎖又有什麼用?所以這就矛盾了

最簡單的解決方案是,事務2再加表鎖之前對錶A進行全表掃描,看一看有沒有行鎖,如果有的話,還要判斷要加的表鎖和行鎖會不會衝突。這種方案真的誇張,加個鎖有必要全表掃描一遍嗎?

事實上InnoDB肯定不會這麼幹的,這裡新增了一種鎖,叫做意向鎖

意向鎖

在InnoDB中,意圖鎖定是表鎖定。它的用途就是說告訴其它的表鎖,現在這個表中有行鎖;如果是讀鎖,那麼就會給表加意圖共享鎖;如果是寫鎖,就會給表加意圖佔有鎖;

事先明確

  • 共享的(S)鎖允許一個事務去讀一行/表。
  • 獨佔的鎖(X)允許一個事務更新或刪除行/表。
  • 意圖共享(IS):事務T 意圖給表T上單獨的tuple設定S 鎖定。
  • 意圖獨佔(IX):事務T 意圖給這些tuple設定X 鎖定。

意圖鎖協議如下:

  • 在假設的事務可以獲得對某假定行的S 鎖定之前,它必須首先獲得對包含該行的表的一個IS 或者更強的鎖定。
  • 在假設的事務可以獲得對某假定行的X 鎖定之前,它必須首先獲得對包含該行的表的一個IX 鎖定。

這些結果可以方便地用一個鎖型別相容矩陣來總結:

X IX S IS
X 衝突 衝突 衝突 衝突
IX 衝突 相容 衝突 相容
S 衝突 衝突 相容 相容
IS 衝突 相容 相容 相容

這個表很明瞭了,假設給表加了IS,那麼就不能給表加寫鎖(X),之前提的問題就迎刃而解了

隱式和顯式鎖定

在事務執行過程中,隨時都可以執行鎖定,鎖只有在執行COMMIT或者ROLLBACK的時候才會釋放,並且所有的鎖是在同一時刻被釋放。諸如SELECT... UPDATE... DELETE... 的是隱式鎖定,InnoDB會根據隔離級別在需要的時候自動加鎖

另外,InnoDB也支援通過特定的語句進行顯式鎖定,這些語句不屬於SQL規範

  • SELECT ... LOCK IN SHARE MODE,顧名思義,這個就是加共享鎖
  • SELECT ... FOR UPDATE,加排它鎖
多版本併發控制(MVCC)

InnoDB儲存引擎實現了行鎖,這確實提高了併發效能,但這只是提高了表層面的併發效能,如果單行資料被頻繁訪問,使用行鎖的話,併發效能還是不夠理想。MySQL的大多數事務型儲存引擎實現的都不是簡單的行級鎖。基於提升併發效能的考慮,它們一般都同時實現了多版本併發控制(MVCC)

關於MVCC的博文很多,不再花時間囉嗦,自己搜去吧

如果你不瞭解MVCC,另外你對髒讀,幻讀,不可重複讀的概念不理解的話,請你立即停止閱讀!接下來的內容會讓你產生頭暈眼花,噁心乾嘔等不良反應

事務隔離級別

InnoDB實現了四種隔離級別:

  1. READ UNCOMMITTED(未提交讀)

    這個級別中,SELECT語句以非鎖定方式被執行。事務中的修改,即使沒有提交,對其他事務也都是可見的。事務可以讀取未提交的資料,這也被稱為髒讀(Dirty Read)。

  2. READ COMMITTED(提交讀)

    一個事務從開始直到提交之前,所做的任何修改對其他事務都是不可見的。這個級別有時候也叫做不可重複讀(nonrepeatable read),因為兩次執行同樣的查詢,可能會得到不一樣的結果

  3. REPEATABLE READ(可重複讀)

    可重複讀隔離級別還是無法解決另外一個幻讀(Phantom Read)的問題。InnoDB和XtraDB儲存引擎通過多版本併發控制(MVCC,Multiversion Concurrency Control)解決了幻讀的問題。

  4. SERIALIZABLE(可序列化)

    SERIALIZABLE是最高的隔離級別。它通過強制事務序列執行,避免了前面說的幻讀的問題。簡單來說,SERIALIZABLE會在讀取的每一行資料上都加鎖,所以可能導致大量的超時和鎖爭用的問題。

各個隔離級別的實現

READ UNCOMMITTED(未提交讀)的實現

SELECT語句以非鎖定方式被執行

READ COMMITTED(提交讀)的實現

這個隔離級別是通過MVCC實現的,事務每次讀的時候,都會讀重新整理過的快照版本,也就是最新的資料。

REPEATABLE READ(可重複讀)的實現

這個隔離級別也是通過MVCC實現的,事務每次讀的時候,都會讀快照版本,也就是舊的版本資料。

SERIALIZABLE(可序列化)的實現

這個級別類似REPEATABLE READ,但是所有無格式SELECT語句被 隱式轉換成SELECT ... LOCK IN SHARE MODE,也就是預設的加共享鎖!

如何避免髒讀,幻讀,不可重複讀

InnoDB的預設隔離級別是RR(REPEATABLE READ),這個級別使用了MVCC+快照讀,完美的解決了不可重複讀和幻讀的問題

雖然RC(READ COMMITTED)也是MVCC實現的,但是每次讀的時候,都會重新整理快照,所以會存在不可重複讀和幻讀的現象

PS:關於幻行,在RR級別,事務A雖然SELECT不到事務B的INSERT,但是可以通過UPDATE,DELETE感知到事務B事務B的INSERT,可以去實驗一下

間隙鎖和next-lock

上面說到了使用MVCC+快照讀其實是可以避免幻讀的,不過還有一種情況沒有考慮到

在InnoDB下,使用SELECT ... FOR UPDATE 和 SELECT ... LOCK IN SHARE MODE時,如何避免幻讀?

單純的使用行鎖的話,是無法阻止幻讀的(想想為什麼);解決這個問題,蠢辦法就是使用表鎖;InnoDB設定者當然不會這麼幹,這裡需要先了解間隙鎖

關於間隙鎖和next-lock。。。。百度去吧,累了