Mysql之加鎖規則

InnoDB鎖

  1. InnoDB實現了兩種標準的行級鎖

    • 共享鎖(S Lock),允許事務讀一行數據
    • 排他鎖(X Lock),允許事務刪除或更新一行數據
  2. 如果一個事務T1已經獲取了行R的共享鎖,那麼另外一個事務T2可以立即獲得行R的共享鎖,因爲讀取並沒有改變行R的數據,稱這種情況爲鎖兼容,如果有其他的事務T3想獲得行R的排他鎖,則必須等待事務T1、T2釋放行R上的共享鎖(稱鎖不兼容)。從下圖可以看出只有S與S鎖兼容,X和S都是行鎖,兼容是指對同一個記錄鎖的兼容情況。

    X S
    X 不兼容 不兼容
    X 不兼容 兼容
  3. 意向鎖:意向鎖是一種不與行級鎖衝突表級鎖

    • 意向鎖共享鎖(IS Lock),事務有意向對錶中的某些行加共享鎖(S鎖)

      -- 事務要獲取某些行的 S 鎖,必須先獲得表的 IS 鎖。
      SELECT column FROM table ... LOCK IN SHARE MODE;
      
    • 意向排他鎖(IX Lock),事務有意向對錶中的某些行加排他鎖

      -- 事務要獲取某些行的 X 鎖,必須先獲得表的 IX 鎖。
      SELECT column FROM table ... FOR UPDATE;
      

行鎖算法

  1. MySQL的行鎖是在引擎層由各個引擎自己實現的。但並不是所有的引擎都支持行鎖,比如MyISAM引擎就不支持行鎖。InnoDB有3種行鎖的算法,分別是

    • Record Lock:單個行記錄上的鎖
    • Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄(是索引)本身
    • Next-Key Lock: Gap Lock+Record Lock,鎖定一個範圍,並且鎖定記錄(是索引)本身
  2. Record Lock總是會鎖定索引記錄,如果在建表時沒有建立任何索引,此時InnoDB引擎會使用隱式的主鍵來進行鎖定

  3. Next-Key Lock是結合了 Gap Lock和Record Lock的一種鎖定算法(行鎖和間隙鎖的組合),在Next-Key Lock算法下,InnoDB對於行的查詢都是採用這種鎖定算法。利用這種鎖定技術鎖定的不是單個值,而是一個範圍(前開後閉區間)。當InnoDB掃描索引記錄的時候,會首先對索引記錄加上行鎖(Record Lock),再對索引記錄兩邊的間隙加上間隙鎖(Gap Lock)

  4. **在InnoDB事務中,行鎖是在需要的時候才加上的,但並不是不需要了就立刻釋放,而是要等到事務結束時才釋放,這個就是兩階段鎖協議。**如果你的事務中需要鎖多個行,要把最可能造成鎖衝突、最可能影響併發度的鎖儘量往後放。

  5. 當併發系統中不同線程出現循環資源依賴,涉及的線程都在等待別的線程釋放資源時,就會導致這幾個線程都進入無限等待的狀態,稱爲死鎖

  6. 如果你要刪除一個表裏面的前10000行數據

    • 執行delete from T limit 10000;
    • 在一個連接中循環執行20次 delete from T limit 500
    • 在20個連接中同時執行delete from T limit 500
    • 第一種方式單個事務的執行時間太長,鎖的時間長,大事務會導致主從延遲。第三種方式會造成鎖衝突,如果能先獲取到所有的id,然後分段,使用第三種方式也可以。

Next-Key Lock

  1. 如果事務T1已經通過next-key locking鎖定了如下範圍

    (10,11](11,13]
    

    當插入新的記錄12時,則鎖定的範圍會變成

    (10,11](11,12](12,13]
    
  2. 當查詢的索引爲唯一索引時,存儲引擎會對Next-Key Lock進行優化,將其降級爲Record Lock,即僅鎖住索引本身,而不是範圍,從而提高應用的併發性。

    DROP TABLE IF EXISTS `t_test`;
    CREATE TABLE `t_test` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      PRIMARY KEY (`id`),
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    
    BEGIN;
    INSERT INTO `t_test` VALUES (1);
    INSERT INTO `t_test` VALUES (2);
    INSERT INTO `t_test` VALUES (4);
    COMMIT;
    
    時間 Session1 Session2
    BEGIN;
    select * from t_test
    where id=4 for update;
    BEGIN;
    INSERT INTO t_test
    VALUES (3);
    成功執行,沒有阻塞
    COMMIT;
    COMMIT;

    表t_test的a列建立了唯一索引,有1、2、4三個值。在Session1種對id=1進行了X鎖定。因爲id是唯一索引,所以鎖定的不是(2,4)這個範圍,而是2,這樣在Session2種插入3不會阻塞,可以立即插入並返回,即降級爲Record Lock

鎖實驗

  1. MySql只有在RR的隔離級別下才有gap lock和next-key lock
  2. 加鎖規則
    • 規則1:加鎖的基本單位是next-key lock(前開後閉區間)
    • 規則2:查找過程中訪問到的對象纔會加鎖
    • 優化1:索引上的等值查詢,給唯一索引加鎖的時候,next-key lock退化爲Record Lock(行鎖)
    • 優化2:索引上的等值查詢,向右遍歷時且最後一個值不滿足等值條件的時候,next-key lock退化爲Gap Lock(間隙鎖)
  3. 默認情況下,InnoDB工作在RR級別下,並且會以Next-Key Lock的方式對數據行進行加鎖,這樣可以有效防止幻讀的發生。以下所有的實驗都是在RR級別下。

準備數據

  1. 建表

    CREATE TABLE `t` (
      `id` int(11) NOT NULL,
      `c` int(11) DEFAULT NULL,
      `d` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `c` (`c`)
    ) ENGINE=InnoDB;
    
    insert into t values(0,0,0),
    (5,5,5),
    (10,10,10),
    (15,15,15),
    (20,20,20),
    (25,25,25);
    
  2. 數據

    +----+------+------+
    | id | c    | d    |
    +----+------+------+
    |  0 |    0 |    0 |
    |  5 |    5 |    5 |
    | 10 |   10 |   10 |
    | 15 |   15 |   15 |
    | 20 |   20 |   20 |
    | 25 |   25 |   25 |
    +----+------+------+
    

等值查詢間隙鎖

  1. 等值查詢間隙鎖

    image-20190905170342398

  2. 分析

    • 由於表t種沒有id=7的記錄,所以根據規則1,加鎖單位是next-key lock,Session1加鎖範圍就是(5,10],因爲id=7是一個等值查詢,根據優化2,id=10不滿足條件,next-key lock退化成Gap Lock,因此最終加鎖的範圍是(5,10)
    • Session2要向這個間隙裏插入id=8的記錄是必須等到Session1的事務提交以後纔可以
    • Session3修改id=10,這個不在加鎖範圍,所以不會阻塞

非唯一索引等值鎖

  1. 非唯一索引等值鎖

    image-20190905173456412

  2. 分析

    • Session1給索引c上的c=5加上讀鎖,根據規則1,加鎖單位爲next-key lock,因爲c是普通索引,所以訪問c=5後,還需要向右遍歷,一直到c=10停止,根據原則2,訪問到的都要加鎖,所以加鎖範圍是(5,10]。根據優化2,等值查詢,退化爲Gap Lock,因此最終加鎖範圍(5,10)

    • Session2要向這個間隙裏插入id=7的記錄是必須等到Session1的事務提交以後纔可以,因爲session 1的間隙鎖範圍是(5,10)

    • 根據原則2,只有訪問到的對象纔會加鎖,這個查詢使用覆蓋索引,並不需要訪問主鍵索引,所以主鍵索引上沒有加任何鎖。所以Session3的語句可以執行成功,不會阻塞

  3. 在這個例子中,lock in share mode只鎖覆蓋索引,但是如果是for update。執行 for update時,系統會認爲你接下來要更新數據,因此會順便給主鍵索引上滿足條件的行加上行鎖

  4. 如果你要用lock in share mode來給行加讀鎖避免數據被更新的話,就必須得繞過覆蓋索引的優化,在查詢字段中加入索引中不存在的字段。比如,將session A的查詢語句改成select d from t where c=5 lock in share mode。

非唯一索引等值鎖for Update

  1. 新的數據準備

    DROP TABLE IF EXISTS `t`;
    CREATE TABLE `t` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `a` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `idx_a` (`a`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    BEGIN;
    INSERT INTO `t` VALUES (2, 1);
    INSERT INTO `t` VALUES (3, 3);
    INSERT INTO `t` VALUES (4, 5);
    INSERT INTO `t` VALUES (5, 8);
    INSERT INTO `t` VALUES (6, 11);
    COMMIT;
    
  2. 因爲表t在a列有索引,所以索引可能被鎖住的範圍爲

    (-∞, 1], (1, 3], (3, 5], (5, 8], (8, 11], (11, +∞)
    
  3. 非唯一索引等值鎖for update

    image-20190905214019569

  4. Session1執行後會鎖住索引行的範圍爲

    (5, 8], (8, 11]
    

    即鎖住了8所在的範圍,還鎖住了下一個範圍,即Next-Key。所以插入12和4都不會阻塞,這個很好理解,但是爲什麼11不會阻塞,5卻阻塞了?似乎與預期並不符合(預期是**(5,11]**之間所有的都會阻塞纔對,而5應該不會阻塞纔對)

  5. 先來看看插入5爲什麼會阻塞,如下圖所示,在索引a上的等值查詢(a=8),給索引a加上了next-key(5, 8], (8, 11],Session1語句的for update會給聚集索引(id=8)加上行鎖(黃色顯示部分).

    image-20190905224657602

    Session2插入5的圖,因爲索引是有序的,並且非聚集索引的葉子節點中的數據是順序存放的,所以對於聚集索引來說,行鎖只鎖住(id=5,a=8)行索引,所以插入是沒問題的,但是對於非聚集索引來說(a=5,id=4)之後是無法插入數據(a=5,id=7)的,因爲鎖的範圍是**( (a=5,id=4), (a=8,id=5) ], ( (a=8,id=5), (a=11,id=6) ]**,(a=5,id=7)在鎖的範圍裏,即無法插入,也就是說只要(a=5,id>4)都是無法插入的.

image-20190905222637937

​ Session2插入11的圖,因爲索引是有序的,所以對於聚集索引來說,行鎖只鎖住(id=5,a=8)行索引,所以插入是沒問題的,對於非聚集索引來說(a=11,id=6)之後是插入數據(a=11,id=7)也是沒問題的,因爲(a=11,id=7)不在鎖的範圍**( (a=5,id=4), (a=8,id=5) ], ( (a=8,id=5), (a=11,id=6) ]****裏,即(a=11,id>6)都是可以插入的

image-20190905224922459

  1. 這裏有一個擴展,如果執行以下SQL會不會阻塞呢?
insert into t(id,a) values(1,5);

其實是不會阻塞的,根據上面的分析,索引是有序的,(a=5,id=1)不在(a=5,id>4)範圍,所以不會阻塞

image-20190905223941624

主鍵索引範圍鎖

  1. 對於以下兩條sql,加鎖範圍不一樣,第一條是id=10的行鎖,第二條是id=10的行鎖和(10,15]的next-key lock

    select * from t where id=10 for update;
    select * from t where id>=10 and id<11 for update;
    
  2. 主鍵索引範圍鎖

    image-20190905180229796

  3. 分析

    • Session1根據規則1,加鎖單位爲next-key lock,因爲是id=10等值查詢,所以退化爲行鎖,id<11範圍查找繼續找,一直到id=15停止,因此next-key lock(10,15]。最終Session1在主鍵索引的加鎖範圍行鎖id=10和next-key lock(10,15]
    • Session2和Session3的分析與上面的一樣

非唯一索引範圍鎖

  1. 非唯一索引範圍鎖

    image-20190905230920532

  2. 分析:Session1索引c上的(5,10] 和(10,15] 這兩個next-key lock

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章