【MySQL】鎖與隔離級別

一、前言

  1. 快照讀的幻讀通過 mvcc 解決
  2. 當前讀的幻讀通過 next-key鎖 解決
  3. 讀提交隔離級別一般沒有 gap lock
  4. 可重複讀隔離級別下,如果觸發了當前讀,那也是要保證事務存續期間的數據一致性的,具體怎麼保證呢?答案是加鎖,阻塞破壞本事務數據一致性的其他事務。

二、表鎖

三、MDL鎖

四、行鎖

行鎖爲InnoDB引擎獨有的,即行鎖是引擎層實現的

(一) 索引與鎖

可重複讀時:
  InnoDB只有在訪問行的時候纔會對其加鎖,而索引能夠減少InnoDB訪問的行數,從而減少鎖的數量。但這隻有當InnoDB在存儲引擎層能夠過濾掉所有不需要的行時纔有效。如果索引無法過濾掉無效的行,那麼在InnoDB檢索到數據並返回給服務器層以後, MySQL 服務器才能應用WHERE子句。 這時已經無法避免鎖定行了: InnoDB 已經鎖住了這些行,事務提交時纔會釋放。即在沒有使用索引的情況下,InnoDB會鎖住整張表

example:

name字段無索引:
+----+---------------------+------+
| id | dat                 | name |
+----+---------------------+------+
|  1 | 2020-01-24 12:00:55 | xxx  |
|  2 | 2020-01-24 12:01:34 | yyy  |
|  3 | 2020-02-23 12:25:24 | zzz  |
+----+---------------------+------+

Session A:
START TRANSACTION WITH CONSISTENT SNAPSHOT;
UPDATE tb_date SET name='zzz' WHERE id=3;

Session B:
UPDATE tb_date SET name='yyy' WHERE name='yyy';

其中Session B會被阻塞住,原因是Session A鎖住了 id=3 這一行,而Session B試圖對所有行進行加鎖

讀提交時:
在MySQL 5.1和更新的版本中,InnoDB可以在服務器端過濾掉行後就釋放鎖,該特性存在於讀提交隔離級別下,可重複讀無該特性

五、gap鎖[1]

  gap鎖之間不互斥,與其他鎖之間也不互斥,只與 INSERT 操作互斥,gap鎖用於鎖間隙。
   讀提交隔離級別一般沒有gap lock ( 不過也有例外情況, 比如insert 出現主鍵衝突的時候,也可能加間隙鎖?在外鍵場景下還是有間隙鎖?)

example:
有表格

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);

執行語句:

Session A:
mysql> BEGIN;
mysql> SELECT d FROM t WHERE c=5 FOR UPDATE;
或
mysql> SELECT id FROM t WHERE c=5 FOR UPDATE;

Session B:
mysql> INSERT INTO t VALUES (8, 8, 8);

上述的 Session B 會被 Session A 阻塞,其原因在於,爲保持Session A 事務存續期間的數據一致性,InnoDB給範圍 c(0, 10) 之間加了間隙鎖(因爲這個範圍內是有可能被其他事務插入c=5的行的)。
總結: Session A 給id=5這一行加了行鎖,同時在索引頁c上加了範圍爲(0, 10)的 gap 鎖 (注:該語句不會在聚簇索引頁上加gap鎖)

覆蓋索引的優化:
執行語句:

Session A:
mysql> BEGIN;
mysql> SELECT id FROM t WHERE c=5 LOCK IN SHARE MODE;

Session B:
mysql> UPDATE t SET d=d+1 WHERE c=5;

Session B 不會被阻塞,原因是只有訪問到的對象纔會加鎖,Session A查詢的是id,可以直接使用c索引頁上的覆蓋索引獲取結果,因此沒有在聚簇索引頁上對 id=5 這一行加鎖,但如果 Session A 執行的是 FOR UPDATE 就不一樣了,因爲系統會認爲你接下來要更新數據,因此會順便給主鍵索引上滿足條件的行加上行鎖。

Note:

  1. 在使用索引查詢的情況下,若所查詢的行不在,則不會加行鎖,但還是會在合適的範圍內加上gap鎖,這依然是爲了保證事務存續期間的數據一致性
  2. 若不使用索引查詢,則會走全表查詢,InnoDB會將所有行加上行鎖,同時會對所有間隙加上gap鎖,事實上,InnoDB不會去隱式的調用表級鎖,鎖整張表就是通過這種方式實現的

六、next-key lock

間隙鎖和行鎖合稱 next-key lock,每個 next-key lock 是前開後閉區間,譬如語句

mysql> SELECT d FROM t WHERE c=5 FOR UPDATE;

加了 next-key鎖(0, 5] 和 gap鎖(5, 10)

加鎖及優化規則[2]
規則1: InnoDB加鎖的基本單位是 next-key lock。next-key lock 是前開後閉區間
規則2: 查找過程中訪問到的對象纔會加鎖
優化1: 索引上的等值查詢,給唯一索引加鎖的時候,next-key lock 退化爲行鎖
優化2: 索引上的等值查詢,向右遍歷時且最後一個值不滿足等值條件的時候,next-key lock 退化爲間隙鎖
規則3: 範圍查詢,向右遍歷查詢時,先確定左邊界的 next-key 鎖,對於唯一性索引,向左搜索到第一個滿足條件的值爲止,對於非唯一索引,向左搜索到第一個不滿足條件的值爲止;再確定右邊界的next-key鎖,不管索引是否唯一,都是向右搜索到第一個不滿足條件的值爲止。向左遍歷查詢時則反之。
例如:

mysql> SELECT * FROM t WHERE id>=10 AND id<=20 FOR UPDATE;

先確定左邊界,id爲唯一性索引,向左搜索到第一個滿足條件的值10,因此加上next-key lock (10, 15],因爲id>=10隱含着等值查詢id=10,根據優化1,需在加上行鎖10,再確定右邊界,向右搜索到第一個不滿足條件的值25,加next-key鎖 (20, 25],得出該語句的加鎖範圍爲 10, (10, 15], (15, 20], (20, 25]
再例如:

mysql> SELECT * FROM t WHERE c>=10 AND c<=20 FOR UPDATE;

先確定左邊界,c爲非唯一性索引,向左搜索到第一個不滿足條件的值5,因此加上next-key lock (5, 10],再確定右邊界,向右搜索到第一個不滿足條件的值25,加next-key鎖 (20, 25],得出該語句的加鎖範圍爲 (5, 10], (10, 15], (15, 20], (20, 25]
規則4: Limit 可減小加鎖範圍[2][3]
規則5: ORDER BY DESC 是會改變遍歷方向的哦[2]

Note: 我們在分析加鎖規則的時候可以用 next-key lock 來分析。但是要知道,具體執行的時候,是要分成間隙鎖和行鎖兩段來執行的,先加gap鎖,再加行鎖[2]

七、如何選擇隔離級別

  互聯網項目中一般把隔離級別設爲讀提交,然後把binlog的格式設爲Row[4]

Ref

[1] Mysql中的GAP鎖(間隙鎖)
[2] (極客時間)爲什麼我只改一行的語句,鎖這麼多?
[3] MySQL 中關於gap lock / next-key lock 的一個問題
[4] 互聯網項目中mysql應該選什麼事務隔離級別

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