超全面的MySQL語句加鎖分析

說在前面的話
mysql

本文是用來系統闡述在MySQL中,不一樣語句在各類條件下的加鎖狀況,並非解釋各類鎖是什麼(或者說加鎖的本質是什麼),你們若是不理解什麼是MVCCReadView正經記錄鎖gap鎖next-key鎖插入意向鎖這些概念的,能夠參考MySQL的官方文檔,或者直接參照《MySQL是怎樣運行的:從根兒上理解MySQL》這本小冊(裏邊有比官方文檔更貼心,更詳細的解釋,文章中涉及到的全部概念均在小冊中有詳細解釋。sql

建議:
        1. 本篇文章不適合碎片化時間閱讀,最好使用電腦觀看,或者將字體跳到最小效果好一些
        2. 可能一會兒看不完,關注 + 收藏 + 好看 + 轉發一波

        3. 不要跳着看
bash


事前準備
微信

創建一個存儲三國英雄的hero表:併發

CREATE TABLE hero (
number INT,
name VARCHAR(100),
country varchar(100),
PRIMARY KEY (number),
KEY idx_name (name)
) Engine=InnoDB CHARSET=utf8;

而後向這個表裏插入幾條記錄:app

INSERT INTO hero VALUES
(1, 'l劉備', '蜀'),
(3, 'z諸葛亮', '蜀'),
(8, 'c曹操', '魏'),
(15, 'x荀彧', '魏'),
(20, 's孫權', '吳');

而後如今hero表就有了兩個索引(一個二級索引,一個聚簇索引),示意圖以下:性能


640?wx_fmt=other

語句加鎖分析

其實啊,「XXX語句該加什麼鎖」自己就是個僞命題,一條語句須要加的鎖受到不少條件制約,比方說:字體

  • 事務的隔離級別優化

  • 語句執行時使用的索引(好比聚簇索引、惟一二級索引、普通二級索引)ui

  • 查詢條件(比方說==<>=等等)

  • 具體執行的語句類型

在繼續詳細分析語句的加鎖過程前,你們必定要有一個全局概念:加鎖只是解決併發事務執行過程當中引發的髒寫髒讀不可重複讀幻讀這些問題的一種解決方案(MVCC算是一種解決髒讀不可重複讀幻讀這些問題的一種解決方案),必定要意識到加鎖的出發點是爲了解決這些問題,不一樣情景下要解決的問題不同,才致使加的鎖不同,千萬不要爲了加鎖而加鎖,容易把本身繞進去。固然,有時候由於MySQL具體的實現而致使一些情景下的加鎖有些不太好理解,這就得咱們死記硬背了~

咱們這裏把語句分爲3種大類:普通的SELECT語句、鎖定讀的語句、INSERT語句,咱們分別看一下。

普通的SELECT語句

普通的SELECT語句在:

  • READ UNCOMMITTED隔離級別下,不加鎖,直接讀取記錄的最新版本,可能發生髒讀不可重複讀幻讀問題。

  • READ COMMITTED隔離級別下,不加鎖,在每次執行普通的SELECT語句時都會生成一個ReadView,這樣解決了髒讀問題,但沒有解決不可重複讀幻讀問題。

  • REPEATABLE READ隔離級別下,不加鎖,只在第一次執行普通的SELECT語句時生成一個ReadView,這樣把髒讀不可重複讀幻讀問題都解決了。

    不過這裏有一個小插曲:

    # 事務T1,REPEATABLE READ隔離級別下
    mysql> BEGIN;
    Query OK, 0 rows affected (0.00 sec)

    mysql> SELECT * FROM hero WHERE number = 30;
    Empty set (0.01 sec)

    # 此時事務T2執行了:INSERT INTO hero VALUES(30, 'g關羽', '魏'); 並提交

    mysql> UPDATE hero SET country = '蜀' WHERE number = 30;
    Query OK, 1 row affected (0.01 sec)
    Rows matched: 1 Changed: 1 Warnings: 0

    mysql> SELECT * FROM hero WHERE number = 30;
    +--------+---------+---------+
    | number | name | country |
    +--------+---------+---------+
    | 30 | g關羽 | 蜀 |
    +--------+---------+---------+
    1 row in set (0.01 sec)

    REPEATABLE READ隔離級別下,T1第一次執行普通的SELECT語句時生成了一個ReadView,以後T2hero表中新插入了一條記錄便提交了,ReadView並不能阻止T1執行UPDATE或者DELETE語句來對改動這個新插入的記錄(由於T2已經提交,改動該記錄並不會形成阻塞),可是這樣一來這條新記錄的trx_id隱藏列就變成了T1事務id,以後T1中再使用普通的SELECT語句去查詢這條記錄時就能夠看到這條記錄了,也就把這條記錄返回給客戶端了。由於這個特殊現象的存在,你也能夠認爲InnoDB中的MVCC並不能完徹底全的禁止幻讀。

  • SERIALIZABLE隔離級別下,須要分爲兩種狀況討論:

    • 在系統變量autocommit=0時,也就是禁用自動提交時,普通的SELECT語句會被轉爲SELECT ... LOCK IN SHARE MODE這樣的語句,也就是在讀取記錄前須要先得到記錄的S鎖,具體的加鎖狀況和REPEATABLE READ隔離級別下同樣,咱們後邊再分析。

    • 在系統變量autocommit=1時,也就是啓用自動提交時,普通的SELECT語句並不加鎖,只是利用MVCC來生成一個ReadView去讀取記錄。

      爲啥不加鎖呢?由於啓用自動提交意味着一個事務中只包含一條語句,一條語句也就沒有啥不可重複讀幻讀這樣的問題了。

鎖定讀的語句

咱們把下邊四種語句放到一塊兒討論:

  • 語句一:SELECT ... LOCK IN SHARE MODE;

  • 語句二:SELECT ... FOR UPDATE;

  • 語句三:UPDATE ...

  • 語句四:DELETE ...

咱們說語句一語句二MySQL中規定的兩種鎖定讀的語法格式,而語句三語句四因爲在執行過程須要首先定位到被改動的記錄並給記錄加鎖,也能夠被認爲是一種鎖定讀

READ UNCOMMITTED/READ COMMITTED隔離級別下

READ UNCOMMITTED下語句的加鎖方式和READ COMMITTED隔離級別下語句的加鎖方式基本一致,因此就放到一起說了。值得注意的是,採用加鎖方式解決併發事務帶來的問題時,其實髒讀不可重複讀在任何一個隔離級別下都不會發生(由於讀-寫操做須要排隊進行)。

對於使用主鍵進行等值查詢的狀況
  • 使用SELECT ... LOCK IN SHARE MODE來爲記錄加鎖,比方說:

    SELECT * FROM hero WHERE number = 8 LOCK IN SHARE MODE;

    這個語句執行時只須要訪問一下聚簇索引中number值爲8的記錄,因此只須要給它加一個S型正經記錄鎖就行了,如圖所示:


    640?wx_fmt=other


  • 使用SELECT ... FOR UPDATE來爲記錄加鎖,比方說:

    SELECT * FROM hero WHERE number = 8 FOR UPDATE;

    這個語句執行時只須要訪問一下聚簇索引中number值爲8的記錄,因此只須要給它加一個X型正經記錄鎖就行了,如圖所示:


    640?wx_fmt=other


    小貼士: 爲了區分S鎖和X鎖,咱們以後在示意圖中就把加了S鎖的記錄染成藍色,把加了X鎖的記錄染成紫色。

  • 使用UPDATE ...來爲記錄加鎖,比方說:

    UPDATE hero SET country = '漢' WHERE number = 8;

    這條UPDATE語句並無更新二級索引列,加鎖方式和上邊所說的SELECT ... FOR UPDATE語句一致。

    若是UPDATE語句中更新了二級索引列,比方說:

    UPDATE hero SET name = 'cao曹操' WHERE number = 8;

    該語句的實際執行步驟是首先更新對應的number值爲8的聚簇索引記錄,再更新對應的二級索引記錄,因此加鎖的步驟就是:

  1. number值爲8的聚簇索引記錄加上X型正經記錄鎖(該記錄對應的)。

  2. 爲該聚簇索引記錄對應的idx_name二級索引記錄(也就是name值爲'c曹操'number值爲8的那條二級索引記錄)加上X型正經記錄鎖

畫個圖就是這樣:


640?wx_fmt=other

小貼士: 咱們用帶圓圈的數字來表示爲各條記錄加鎖的順序。

使用DELETE ...來爲記錄加鎖,比方說:

DELETE FROM hero WHERE number = 8;

咱們平時所說的「DELETE表中的一條記錄」其實意味着對聚簇索引和全部的二級索引中對應的記錄作DELETE操做,本例子中就是要先把number值爲8的聚簇索引記錄執行DELETE操做,而後把對應的idx_name二級索引記錄刪除,因此加鎖的步驟和上邊更新帶有二級索引列的UPDATE語句一致,就不畫圖了。

對於使用主鍵進行範圍查詢的狀況
  • 使用SELECT ... LOCK IN SHARE MODE來爲記錄加鎖,比方說:

    SELECT * FROM hero WHERE number <= 8 LOCK IN SHARE MODE;

    這個語句看起來十分簡單,但它的執行過程仍是有一丟丟小複雜的:

  1. 先到聚簇索引中定位到知足number <= 8的第一條記錄,也就是number值爲1的記錄,而後爲其加鎖。

  2. 判斷一下該記錄是否符合索引條件下推中的條件。

    咱們前邊介紹過一個稱之爲索引條件下推( Index Condition Pushdown,簡稱ICP)的功能,也就是把查詢中與被使用索引有關的查詢條件下推到存儲引擎中判斷,而不是返回到server層再判斷。不過須要注意的是,索引條件下推只是爲了減小回表次數,也就是減小讀取完整的聚簇索引記錄的次數,從而減小IO操做。而對於聚簇索引而言不須要回表,它自己就包含着所有的列,也起不到減小IO操做的做用,因此設計InnoDB的大叔們規定這個索引條件下推特性只適用於二級索引。也就是說在本例中與被使用索引有關的條件是:number <= 8,而number列又是聚簇索引列,因此本例中並無符合索引條件下推的查詢條件,天然也就不須要判斷該記錄是否符合索引條件下推中的條件。

  3. 判斷一下該記錄是否符合範圍查詢的邊界條件

    由於在本例中是利用主鍵number進行範圍查詢,設計InnoDB的大叔規定每從聚簇索引中取出一條記錄時都要判斷一下該記錄是否符合範圍查詢的邊界條件,也就是number <= 8這個條件。若是符合的話將其返回給server層繼續處理,不然的話須要釋放掉在該記錄上加的鎖,並給server層返回一個查詢完畢的信息。

    對於number值爲1的記錄是符合這個條件的,因此會將其返回到server層繼續處理。

  4. 將該記錄返回到server層繼續判斷。

    server層若是收到存儲引擎層提供的查詢完畢的信息,就結束查詢,不然繼續判斷那些沒有進行索引條件下推的條件,在本例中就是繼續判斷number <= 8這個條件是否成立。噫,不是在第3步中已經判斷過了麼,怎麼在這又判斷一回?是的,設計InnoDB的大叔採用的策略就是這麼簡單粗暴,把凡是沒有通過索引條件下推的條件都須要放到server層再判斷一遍。若是該記錄符合剩餘的條件(沒有進行索引條件下推的條件),那麼就把它發送給客戶端,否則的話須要釋放掉在該記錄上加的鎖。

  5. 而後剛剛查詢獲得的這條記錄(也就是number值爲1的記錄)組成的單向鏈表繼續向後查找,獲得了number值爲3的記錄,而後重複第2345這幾個步驟。

小貼士: 上述步驟是在MySQL 5.7.21這個版本中驗證的,不保證其餘版本有無出入。

可是這個過程有個問題,就是當找到number值爲8的那條記錄的時候,還得向後找一條記錄(也就是number值爲15的記錄),在存儲引擎讀取這條記錄的時候,也就是上述的第1步中,就得爲這條記錄加鎖,而後在第3步時,判斷該記錄不符合number <= 8這個條件,又要釋放掉這條記錄的鎖,這個過程致使number值爲15的記錄先被加鎖,而後把鎖釋放掉,過程就是這樣:

640?wx_fmt=other


這個過程有意思的一點就是,若是你先在事務T1中執行:

# 事務T1
BEGIN;
SELECT * FROM hero WHERE number <= 8 LOCK IN SHARE MODE;

而後再到事務T2中執行:

# 事務T2
BEGIN;
SELECT * FROM hero WHERE number = 15 FOR UPDATE;

是沒有問題的,由於在T2執行時,事務T1已經釋放掉了number值爲15的記錄的鎖,可是若是你先執行T2,再執行T1,因爲T2已經持有了number值爲15的記錄的鎖,事務T1將由於獲取不到這個鎖而等待。

咱們再看一個使用主鍵進行範圍查詢的例子:

SELECT * FROM hero WHERE number >= 8 LOCK IN SHARE MODE;

這個語句的執行過程其實和咱們舉的上一個例子相似。也是先到聚簇索引中定位到知足number >= 8這個條件的第一條記錄,也就是number值爲8的記錄,而後就能夠沿着由記錄組成的單向鏈表一路向後找,每找到一條記錄,就會爲其加上鎖,而後判斷該記錄符不符合範圍查詢的邊界條件,不過這裏的邊界條件比較特殊:number >= 8,只要記錄不小於8就算符合邊界條件,因此判斷和沒判斷是同樣同樣的。最後把這條記錄返回給server層server層再判斷number >= 8這個條件是否成立,若是成立的話就發送給客戶端,不然的話就結束查詢。不過InnoDB存儲引擎找到索引中的最後一條記錄,也就是Supremum僞記錄以後,在存儲引擎內部就能夠當即判斷這是一條僞記錄,沒必要要返回給server層處理,也不必給它也加上鎖(也就是說在第1步中就壓根兒沒給這條記錄加鎖)。整個過程會給number值爲81520這三條記錄加上S型正經記錄鎖,畫個圖表示一下就是這樣:

640?wx_fmt=other

使用SELECT ... FOR UPDATE語句來爲記錄加鎖:

SELECT ... FOR UPDATE語句相似,只不過加的是X型正經記錄鎖

使用UPDATE ...來爲記錄加鎖,比方說:

UPDATE hero SET country = '漢' WHERE number >= 8;

這條UPDATE語句並無更新二級索引列,加鎖方式和上邊所說的SELECT ... FOR UPDATE語句一致。

若是UPDATE語句中更新了二級索引列,比方說:

UPDATE hero SET name = 'cao曹操' WHERE number >= 8;

這時候會首先更新聚簇索引記錄,再更新對應的二級索引記錄,因此加鎖的步驟就是:

  1. number值爲8的聚簇索引記錄加上X型正經記錄鎖

  2. 而後爲上一步中的記錄索引記錄對應的idx_name二級索引記錄加上X型正經記錄鎖

  3. number值爲15的聚簇索引記錄加上X型正經記錄鎖

  4. 而後爲上一步中的記錄索引記錄對應的idx_name二級索引記錄加上X型正經記錄鎖

  5. number值爲20的聚簇索引記錄加上X型正經記錄鎖

  6. 而後爲上一步中的記錄索引記錄對應的idx_name二級索引記錄加上X型正經記錄鎖

畫個圖就是這樣:

640?wx_fmt=other

若是是下邊這個語句:

UPDATE hero SET namey = '漢' WHERE number <= 8;

則會對number值爲138聚簇索引記錄以及它們對應的二級索引記錄加X型正經記錄鎖,加鎖順序和上邊語句中的加鎖順序相似,都是先對一條聚簇索引記錄加鎖後,再給對應的二級索引記錄加鎖。以後會繼續對number值爲15的聚簇索引記錄加鎖,可是隨後InnoDB存儲引擎判斷它不符合邊界條件,隨即會釋放掉該聚簇索引記錄上的鎖(注意這個過程當中沒有對number值爲15的聚簇索引記錄對應的二級索引記錄加鎖)。具體示意圖就不畫了。

使用DELETE ...來爲記錄加鎖,比方說:

DELETE FROM hero WHERE number >= 8;

DELETE FROM hero WHERE number <= 8;

這兩個語句的加鎖狀況和更新帶有二級索引列的UPDATE語句一致,就不畫圖了。

對於使用二級索引進行等值查詢的狀況

小貼士: 在READ UNCOMMITTED和READ COMMITTED隔離級別下,使用普通的二級索引和惟一二級索引進行加鎖的過程是同樣的,因此咱們也就不分開討論了。

  • 使用SELECT ... LOCK IN SHARE MODE來爲記錄加鎖,比方說:

    SELECT * FROM hero WHERE name = 'c曹操' LOCK IN SHARE MODE;

    這個語句的執行過程是先經過二級索引idx_name定位到知足name = 'c曹操'條件的二級索引記錄,而後進行回表操做。因此先要對二級索引記錄加S型正經記錄鎖,而後再給對應的聚簇索引記錄加S型正經記錄鎖,示意圖以下:


    640?wx_fmt=other


    這裏須要再次強調一下這個語句的加鎖順序:



  1. 先對name列爲'c曹操'二級索引記錄進行加鎖。

  2. 再對相應的聚簇索引記錄進行加鎖


小貼士: 咱們知道idx_name是一個普通的二級索引,到idx_name索引中定位到知足name= 'c曹操'這個條件的第一條記錄後,就能夠沿着這條記錄一路向後找。但是從咱們上邊的描述中能夠看出來,並無對下一條二級索引記錄進行加鎖,這是爲何呢?這是由於設計InnoDB的大叔對等值匹配的條件有特殊處理,他們規定在InnoDB存儲引擎層查找到當前記錄的下一條記錄時,在對其加鎖前就直接判斷該記錄是否知足等值匹配的條件,若是不知足直接返回(也就是不加鎖了),不然的話須要將其加鎖後再返回給server層。因此這裏也就不須要對下一條二級索引記錄進行加鎖了。

如今要介紹一個很是有趣的事情,咱們假設上邊這個語句在事務T1中運行,而後事務T2中運行下邊一個咱們以前介紹過的語句:

UPDATE hero SET name = '曹操' WHERE number = 8;

這兩個語句都是要對number值爲8的聚簇索引記錄和對應的二級索引記錄加鎖,可是不一樣點是加鎖的順序不同。這個UPDATE語句是先對聚簇索引記錄進行加鎖,後對二級索引記錄進行加鎖,若是在不一樣事務中運行上述兩個語句,可能發生一種賊奇妙的事情 ——

  • 事務T2持有了聚簇索引記錄的鎖,事務T1持有了二級索引記錄的鎖。

  • 事務T2在等待獲取二級索引記錄上的鎖,事務T1在等待獲取聚簇索引記錄上的鎖。

兩個事務都分別持有一個鎖,並且都在等待對方已經持有的那個鎖,這種狀況就是所謂的死鎖,兩個事務都沒法運行下去,必須選擇一個進行回滾,對性能影響比較大。  

使用SELECT ... FOR UPDATE語句時,好比:

SELECT * FROM hero WHERE name = 'c曹操' FOR UPDATE;

這種狀況下與SELECT ... LOCK IN SHARE MODE語句的加鎖狀況相似,都是給訪問到的二級索引記錄和對應的聚簇索引記錄加鎖,只不過加的是X型正經記錄鎖罷了。

使用UPDATE ...來爲記錄加鎖,比方說:

與更新二級索引記錄的SELECT ... FOR UPDATE的加鎖狀況相似,不過若是被更新的列中還有別的二級索引列的話,對應的二級索引記錄也會被加鎖。

使用DELETE ...來爲記錄加鎖,比方說:

SELECT ... FOR UPDATE的加鎖狀況相似,不過若是表中還有別的二級索引列的話,對應的二級索引記錄也會被加鎖。

對於使用二級索引進行範圍查詢的狀況
  • 使用SELECT ... LOCK IN SHARE MODE來爲記錄加鎖,比方說:

    SELECT * FROM hero FORCE INDEX(idx_name) WHERE name >= 'c曹操' LOCK IN SHARE MODE;

    小貼士: 由於優化器會計算使用二級索引進行查詢的成本,在成本較大時可能選擇以全表掃描的方式來執行查詢,因此咱們這裏使用FORCE INDEX(idx_name)來強制使用二級索引idx_name來執行查詢。

    這個語句的執行過程實際上是先到二級索引中定位到知足name >= 'c曹操'的第一條記錄,也就是name值爲c曹操的記錄,而後就能夠沿着這條記錄的鏈表一路向後找,從二級索引idx_name的示意圖中能夠看出,全部的用戶記錄都知足name >= 'c曹操'的這個條件,因此全部的二級索引記錄都會被加S型正經記錄鎖,它們對應的聚簇索引記錄也會被加S型正經記錄鎖。不過須要注意一下加鎖順序,對一條二級索引記錄加鎖完後,會接着對它相應的聚簇索引記錄加鎖,完後纔會對下一條二級索引記錄進行加鎖,以此類推~ 畫個圖表示一下就是這樣:


    640?wx_fmt=other


    再來看下邊這個語句:

    SELECT * FROM hero FORCE INDEX(idx_name) WHERE name <= 'c曹操' LOCK IN SHARE MODE;

    這個語句的加鎖狀況就有點兒有趣了。前邊說在使用number <= 8這個條件的語句中,須要把number值爲15的記錄也加一個鎖,以後又判斷它不符合邊界條件而把鎖釋放掉。而對於查詢條件name <= 'c曹操'的語句來講,執行該語句須要使用到二級索引,而與二級索引相關的條件是可使用索引條件下推這個特性的。設計InnoDB的大叔規定,若是一條記錄不符合索引條件下推中的條件的話,直接跳到下一條記錄(這個過程根本不將其返回到server層),若是這已是最後一條記錄,那麼直接向server層報告查詢完畢。可是這裏頭有個問題呀:先對一條記錄加了鎖,而後再判斷該記錄是否是符合索引條件下推的條件,若是不符合直接跳到下一條記錄或者直接向server層報告查詢完畢,這個過程當中並無把那條被加鎖的記錄上的鎖釋放掉呀!!!。本例中使用的查詢條件是name <= 'c曹操',在爲name值爲'c曹操'的二級索引記錄以及它對應的聚簇索引加鎖以後,會接着二級索引中的下一條記錄,也就是name值爲'l劉備'的那條二級索引記錄,因爲該記錄不符合索引條件下推的條件,並且是範圍查詢的最後一條記錄,會直接向server層報告查詢完畢,重點是這個過程當中並不會釋放name值爲'l劉備'的二級索引記錄上的鎖,也就致使了語句執行完畢時的加鎖狀況以下所示:


    640?wx_fmt=other


    這樣子會形成一個尷尬狀況,假如T1執行了上述語句而且還沒有提交,T2再執行這個語句:

    SELECT * FROM hero WHERE name = 'l劉備' FOR UPDATE;

    T2中的語句須要獲取name值爲l劉備的二級索引記錄上的X型正經記錄鎖,而T1中仍然持有name值爲l劉備的二級索引記錄上的S型正經記錄鎖,這就形成了T2獲取不到鎖而進入等待狀態。

    小貼士: 爲啥不能釋放不符合索引條件下推中的條件的二級索引記錄上的鎖呢?這個問題我也沒想明白,人家就是這麼規定的,若是有明白的小夥伴能夠加我微信 xiaohaizi4919 來討論一下哈~ 再強調一下,我使用的MySQL版本是5.7.21,不保證其餘版本中的加鎖情景是否徹底一致。

  • 使用SELECT ... FOR UPDATE語句時:

    SELECT ... FOR UPDATE語句相似,只不過加的是X型正經記錄鎖

  • 使用UPDATE ...來爲記錄加鎖,比方說:

    UPDATE hero SET country = '漢' WHERE name >= 'c曹操';

    小貼士: FORCE INDEX只對SELECT語句起做用,UPDATE語句雖然支持該語法,但實質上不起做用,DELETE語句壓根兒不支持該語法。

    假設該語句執行時使用了idx_name二級索引來進行鎖定讀,那麼它的加鎖方式和上邊所說的SELECT ... FOR UPDATE語句一致。若是有其餘二級索引列也被更新,那麼也會爲對應的二級索引記錄進行加鎖,就不贅述了。不過還有一個有趣的狀況,比方說:

    UPDATE hero SET country = '漢' WHERE name <= 'c曹操';

    咱們前邊說的索引條件下推這個特性只適用於SELECT語句,也就是說UPDATE語句中沒法使用,那麼這個語句就會爲name值爲'c曹操''l劉備'的二級索引記錄以及它們對應的聚簇索引進行加鎖,以後在判斷邊界條件時發現name值爲'l劉備'的二級索引記錄不符合name <= 'c曹操'條件,再把該二級索引記錄和對應的聚簇索引記錄上的鎖釋放掉。這個過程以下圖所示:


    640?wx_fmt=other


  • 使用DELETE ...來爲記錄加鎖,比方說:

    DELETE FROM hero WHERE name >= 'c曹操';

    DELETE FROM hero WHERE name <= 'c曹操';

    若是這兩個語句採用二級索引來進行鎖定讀,那麼它們的加鎖狀況和更新帶有二級索引列的UPDATE語句一致,就不畫圖了。

全表掃描的狀況

比方說:

SELECT * FROM hero WHERE country = '魏' LOCK IN SHARE MODE;

因爲country列上未建索引,因此只能採用全表掃描的方式來執行這條查詢語句,存儲引擎每讀取一條聚簇索引記錄,就會爲這條記錄加鎖一個S型正常記錄鎖,而後返回給server層,若是server層判斷country = '魏'這個條件是否成立,若是成立則將其發送給客戶端,不然會釋放掉該記錄上的鎖,畫個圖就像這樣:


640?wx_fmt=other


使用SELECT ... FOR UPDATE進行加鎖的狀況與上邊相似,只不過加的是X型正經記錄鎖,就不贅述了。

對於UPDATE ...DELETE ...的語句來講,在遍歷聚簇索引中的記錄,都會爲該聚簇索引記錄加上X型正經記錄鎖,而後:

  • 若是該聚簇索引記錄不知足條件,直接把該記錄上的鎖釋放掉。

  • 若是該聚簇索引記錄知足條件,則會對相應的二級索引記錄加上X型正經記錄鎖DELETE語句會對全部二級索引列加鎖,UPDATE語句只會爲更新的二級索引列對應的二級索引記錄加鎖)。



—————END—————



小灰建立了一個免費的知識星球,裏面有許多有趣的搶答活動,

還有各類獎品,歡迎你們掃碼加入:


640?wx_fmt=png