MySQL死鎖系列-常見加鎖場景分析

在上一篇文章《鎖的類型以及加鎖原理》主要總結了 MySQL 鎖的類型和模式以及基本的加鎖原理,今天咱們就從原理走向實戰,分析常見 SQL 語句的加鎖場景。瞭解了這幾種場景,相信小夥伴們也能觸類旁通,靈活地分析真實開發過程當中遇到的加鎖問題。sql

以下圖所示,數據庫的隔離等級,SQL 語句和當前數據庫數據會共同影響該條 SQL 執行時數據庫生成的鎖模式,鎖類型和鎖數量數據庫

下面,咱們會首先講解一下隔離等級、不一樣 SQL 語句 和 當前數據庫數據對生成鎖影響的基本規則,而後再依次具體 SQL 的加鎖場景。併發

隔離等級對加鎖的影響

MySQL 的隔離等級對加鎖有影響,因此在分析具體加鎖場景時,首先要肯定當前的隔離等級工具

  • 讀未提交(Read Uncommitted 後續簡稱 RU):能夠讀到未提交的讀,基本上不會使用該隔離等級,因此暫時忽略。
  • 讀已提交(Read Committed 後續簡稱 RC):存在幻讀問題,對當前讀獲取的數據加記錄鎖
  • 可重複讀(Repeatable Read 後續簡稱 RR):不存在幻讀問題,對當前讀獲取的數據加記錄鎖,同時對涉及的範圍加間隙鎖,防止新的數據插入,致使幻讀。
  • 序列化(Serializable):從 MVCC 併發控制退化到基於鎖的併發控制,不存在快照讀,都是當前讀,併發效率急劇降低,不建議使用。

這裏說明一下,RC 老是讀取記錄的最新版本,而 RR 是讀取該記錄事務開始時的那個版本,雖然這兩種讀取的版本不一樣,可是都是快照數據,並不會被寫操做阻塞,因此這種讀操做稱爲 快照讀(Snapshot Read).net

MySQL 還提供了另外一種讀取方式叫當前讀(Current Read),它讀的再也不是數據的快照版本,而是數據的最新版本,並會對數據加鎖,根據語句和加鎖的不一樣,又分紅三種狀況:線程

  • SELECT ... LOCK IN SHARE MODE:加共享(S)鎖
  • SELECT ... FOR UPDATE:加排他(X)鎖
  • INSERT / UPDATE / DELETE:加排他(X)鎖

當前讀在 RR 和 RC 兩種隔離級別下的實現也是不同的:RC 只加記錄鎖,RR 除了加記錄鎖,還會加間隙鎖,用於解決幻讀問題日誌

不一樣 SQL 語句對加鎖的影響

不一樣的 SQL 語句固然會加不一樣的鎖,總結起來主要分爲五種狀況:blog

  • SELECT ... 語句正常狀況下爲快照讀,不加鎖;
  • SELECT ... LOCK IN SHARE MODE 語句爲當前讀,加 S 鎖;
  • SELECT ... FOR UPDATE 語句爲當前讀,加 X 鎖;
  • 常見的 DML 語句(如 INSERT、DELETE、UPDATE)爲當前讀,加 X 鎖;
  • 常見的 DDL 語句(如 ALTER、CREATE 等)加表級鎖,且這些語句爲隱式提交,不能回滾。

其中,當前讀的 SQL 語句的 where 從句的不一樣也會影響加鎖,包括是否使用索引,索引是不是惟一索引等等。索引

當前數據對加鎖的影響

SQL 語句執行時數據庫中的數據也會對加鎖產生影響。事務

好比一條最簡單的根據主鍵進行更新的 SQL 語句,若是主鍵存在,則只須要對其加記錄鎖,若是不存在,則須要在加間隙鎖。

至於其餘非惟一性索引更新或者插入時的加鎖也都不一樣程度的受到現存數據的影響,後續咱們會一一說明。

具體場景分析

具體 SQL 場景分析主要借鑑何登成前輩的《MySQL 加鎖處理分析》文章和 aneasystone 的系列文章,在他們的基礎上進行了總結和整理。

咱們使用下面這張 book 表做爲實例,其中 id 爲主鍵,ISBN(書號)爲二級惟一索引,Author(做者)爲二級非惟一索引,score(評分)無索引。

UPDATE 語句加鎖分析

下面,咱們先來分析 UPDATE 相關 SQL 在使用較爲簡單 where 從句狀況下加鎖狀況。其中的分析原則也適用於 UPDATE,DELETE 和 SELECT ... FOR UPDATE等當前讀的語句。

聚簇索引,查詢命中

聚簇索引就是 InnoDB 存儲引擎下的主鍵索引,具體可參考《MySQL索引》

下圖展現了使用 UPDATE book SET score = 9.2 WHERE ID = 10 語句命中的狀況下在 RC 和 RR 隔離等級下的加鎖,兩種隔離等級下沒有任何區別,都是對 ID = 10 這個索引加排他記錄鎖。

聚簇索引,查詢未命中

下圖展現了 UPDATE book SET score = 9.2 WHERE ID = 16 語句未命中時 RR 隔離級別下的加鎖狀況。

在 RC 隔離等級下,不須要加鎖;而在 RR 隔離級別會在 ID = 16 先後兩個索引之間加上間隙鎖。

值得注意的是,間隙鎖和間隙鎖之間是互不衝突的,間隙鎖惟一的做用就是爲了防止其餘事務的插入新行,致使幻讀,因此加間隙 S 鎖和加間隙 X 鎖沒有任何區別。

二級惟一索引,查詢命中

下圖展現了 UPDATE book SET score = 9.2 WHERE ISBN = 'N0003' 在 RC 和 RR 隔離等級下命中時的加鎖狀況。

在 InnoDB 存儲引擎中,二級索引的葉子節點保存着主鍵索引的值,而後再拿主鍵索引去獲取真正的數據行,因此在這種狀況下,二級索引和主鍵索引都會加排他記錄鎖。

二級惟一索引,查詢未命中

下圖展現了 UPDATE book SET score = 9.2 WHERE ISBN = 'N0008' 語句在 RR 隔離等級下未命中時的加鎖狀況,RC 隔離等級下該語句未命中不會加鎖。

由於 N0008 大於 N0007,因此要鎖住 (N0007,正無窮)這段區間,而 InnoDB 的索引通常都使用 Suprenum Record 和 Infimum Record 來分別表示記錄的上下邊界。Infimum 是比該頁中任何記錄都要小的值,而 Supremum 比該頁中最大的記錄值還要大,這兩條記錄在建立頁的時候就有了,而且不會刪除。

因此,在 N0007 和 Suprenum Record 之間加了間隙鎖。

爲何不在主鍵上也加 GAP 鎖呢?歡迎留言說出你的想法。

二級非惟一索引,查詢命中

下圖展現了 UPDATE book SET score = 9.2 WHERE Author = 'Tom' 語句在 RC 隔離等級下命中時的加鎖狀況。

咱們能夠看到,在 RC 等級下,二級惟一索引和二級非惟一索引的加鎖狀況是一致的,都是在涉及的二級索引和對應的主鍵索引上加上排他記錄鎖。

可是在 RR 隔離等級下,加鎖的狀況產生了變化,它不只對涉及的二級索引和主鍵索引加了排他記錄鎖,還在非惟一二級索引上加了三個間隙鎖,鎖住了兩個 Tom 索引值相關的三個範圍。

那爲何惟一索引不須要加間隙鎖呢?間隙鎖的做用是爲了解決幻讀,防止其餘事務插入相同索引值的記錄,而惟一索引和主鍵約束都已經保證了該索引值確定只有一條記錄,因此無需加間隙鎖。

須要注意的是,上圖雖然畫着 4 個記錄鎖,三個間隙鎖,可是實際上間隙鎖和它右側的記錄鎖會合併成 Next-Key 鎖。

因此實際狀況有兩個 Next-Key 鎖,一個間隙鎖(Tom60,正無窮)和兩個記錄鎖。

二級非惟一索引,查詢未命中

下圖展現了 UPDATE book SET score = 9.2 WHERE Author = 'Sarah' 在 RR 隔離等級下未命中的加鎖狀況,它會在二級索引 Rose 和 Tom 之間加間隙鎖。而 RC 隔離等級下不須要加鎖。

無索引

當 Where 從句的條件並不使用索引時,則會對全表進行掃描,在 RC 隔離等級下對全部的數據加排他記錄鎖。在RR 隔離等級下,除了給記錄加鎖,還會對記錄和記錄之間加間隙鎖。和上邊同樣,間隙鎖會和左側的記錄鎖合併成 Next-Key 鎖。

下圖就是 UPDATE book SET score = 9.2 WHERE score = 22 語句在兩種隔離等級下的加鎖狀況。

聚簇索引,範圍查詢

上面介紹的場景都是 where 從句的等值查詢,而範圍查詢的加鎖又是怎麼樣的呢?咱們慢慢來看。

下圖是 UPDATE book SET score = 9.2 WHERE ID <= 25 在 RC 和 RR 隔離等級下的加鎖狀況。

RC 場景下與等值查詢相似,只會在涉及的 ID = 10,ID = 18 和 ID = 25 索引上加排他記錄鎖。

而在 RR 隔離等級下則有所不一樣,它會加上間隙鎖,和對應的記錄鎖合併稱爲 Next-Key 鎖。除此以外,它還會在(25, 30] 上分別加 Next-Key 鎖。這一點是十分特殊的,具體緣由還須要再探究。

二級索引,範圍查詢

下圖展現了 UPDATE book SET ISBN = N0001 WHERE score <= 7.9 在 RR 級別下的加鎖狀況。

修改索引值

UPDATE 語句修改索引值的狀況能夠分開分析,首先 Where 從句的加鎖分析如上文所述,多了一步 Set 部分的加鎖。

下圖展現了 UPDATE book SET Author = 'John' WHERE ID = 10 在 RC 和 RR 隔離等級下的加鎖狀況。除了在主鍵 ID 上進行加鎖,還會對二級索引上的 Bob(就值) 和 John(新值) 上進行加鎖。

DELETE 語句加鎖分析

通常來講,DELETE 的加鎖和 SELECT FOR UPDATE 或 UPDATE 並無太大的差別。

由於,在 MySQL 數據庫中,執行 DELETE 語句其實並無直接刪除記錄,而是在記錄上打上一個刪除標記,而後經過後臺的一個叫作 purge 的線程來清理。從這一點來看,DELETE 和 UPDATE 確實是很是相像。事實上,DELETE 和 UPDATE 的加鎖也幾乎是同樣的。

INSERT 語句加鎖分析

接下來,咱們來看一下 Insert 語句的加鎖狀況。

Insert 語句在兩種狀況下會加鎖:

  • 爲了防止幻讀,若是記錄之間加有間隙鎖,此時不能 Insert;
  • 若是 Insert 的記錄和已有記錄形成惟一鍵衝突,此時不能 Insert;

除了上述狀況,Insert 語句的鎖都是隱式鎖。隱式鎖是 InnoDB 實現的一種延遲加鎖的機制來減小加鎖的數量。

隱式鎖的特色是隻有在可能發生衝突時才加鎖,減小了鎖的數量。另外,隱式鎖是針對被修改的 B+Tree 記錄,所以都是記錄類型的鎖,不多是間隙鎖或 Next-Key 類型。

具體 Insert 語句的加鎖流程以下:

  • 首先對插入的間隙加插入意向鎖(Insert Intension Locks)

    • 若是該間隙已被加上了間隙鎖或 Next-Key 鎖,則加鎖失敗進入等待;
    • 若是沒有,則加鎖成功,表示能夠插入;
  • 而後判斷插入記錄是否有惟一鍵,若是有,則進行惟一性約束檢查

    • 若是不存在相同鍵值,則完成插入

    • 若是存在相同鍵值,則判斷該鍵值是否加鎖

      • 若是沒有鎖, 判斷該記錄是否被標記爲刪除

        • 若是標記爲刪除,說明事務已經提交,還沒來得及 purge,這時加 S 鎖等待;
        • 若是沒有標記刪除,則報 duplicate key 錯誤;
      • 若是有鎖,說明該記錄正在處理(新增、刪除或更新),且事務還未提交,加 S 鎖等待;

  • 插入記錄並對記錄加 X 記錄鎖;

後記

本文中講解的 SQL 語句都是十分簡單的,當 SQL 語句包含多個查詢條件時,加鎖的分析過程就每每更加複雜。咱們須要使用 MySQL 相關的工具進行分析,而且有時甚至須要查詢 MySQL 相關的日誌信息來了解到底語句加了什麼鎖或者爲何產生死鎖,下篇文章中咱們就主要了解一下這些內容,請你們持續關注。

我的博客,歡迎來玩

相關文章
相關標籤/搜索