寫在前面
資料庫本質上是一種共享資源
,因此在最大程度提供併發訪問效能的同時,仍需要確保每個使用者能以一致的方式讀取和修改資料。鎖機制(Locking)
就是解決這類問題的最好武器。
首先新建表 test
,其中 id
為主鍵,name
為輔助索引,address
為唯一索引。
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` int(11) NOT NULL,
`address` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idex_unique` (`address`),
KEY `idx_index` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
複製程式碼
INSERT 方法中的行鎖
可見,如果兩個事務先後對主鍵相同的行記錄執行 INSERT
操作,因為事務 A
先拿到了行鎖,事務 B
只能等待直到事務 A
提交後行鎖被釋放。同理,如果針對唯一索引欄位 address
進行插入操作,也需要獲取行鎖,圖同主鍵插入過程類似,不再重複。
但是,如果兩個事務都針對輔助索引欄位 name 進行插入,不需要等待獲取鎖,因為輔助索引欄位即使值相同,在資料庫中也是操作不同的記錄行,不會衝突。
Update
方法與 Insert
方法結果類似。
SELECT FOR UPDATE 下的表鎖與行鎖
事務 A
SELECT FOR UPDATE
語句會拿到表 test
的 Table Lock
,此時事務 B
去執行插入操作會阻塞,直到事務 A
提交釋放表鎖後,事務 B
才能獲取對應的行鎖執行插入操作。
但是如果事務 A 的 SELECT FOR UPDATE 語句緊跟 WHERE id = 1 的話,那麼這條語句只會獲取行鎖,不會是表鎖,此時不阻塞事務 B 對於其他主鍵的修改操作
輔助索引下的間隙鎖
先看下 test
表下的資料情況:
mysql> select * from test;
+----+------+---------+
| id | name | address |
+----+------+---------+
| 3 | 1 | 3 |
| 6 | 1 | 2 |
| 7 | 2 | 4 |
| 8 | 10 | 5 |
+----+------+---------+
4 rows in set (0.00 sec)
複製程式碼
間隙鎖可以說是行鎖的一種,不同的是它鎖住的是一個範圍內的記錄,作用是避免幻讀,即區間資料條目的突然增減。解決辦法主要是:
- 防止間隙內有新資料被插入,因此叫間隙鎖
- 防止已存在的資料,在更新操作後成為間隙內的資料(例如更新
id = 7
的name
欄位為1
,那麼name = 1
的條數就從2
變為3
)
InnoDB
自動使用間隙鎖的條件為:
Repeatable Read
隔離級別,這是MySQL
的預設工作級別- 檢索條件必須有索引(沒有索引的話會走全表掃描,那樣會鎖定整張表所有的記錄)
當 InnoDB
掃描索引記錄的時候,會首先對選中的索引行記錄加上行鎖,再對索引記錄兩邊的間隙(向左掃描掃到第一個比給定引數小的值, 向右掃描掃描到第一個比給定引數大的值, 以此構建一個區間)加上間隙鎖。如果一個間隙被事務 A
加了鎖,事務 B
是不能在這個間隙插入記錄的。
我們這裡所說的 “間隙鎖” 其實不是 GAP LOCK,而是 RECORD LOCK + GAP LOCK,InnoDB 中稱之為 NEXT_KEY LOCK
下面看個例子,我們建表時指定 name
列為輔助索引,目前這列的取值有 [1,2,10]。間隙範圍有 (-∞, 1]、[1,1]、[1,2]、[2,10]、[10, +∞)
Round 1:
- 事務 A SELECT ... WHERE name = 1 FOR UPDATE;
- 對 (-∞, 2) 增加間隙鎖
- 事務 B INSERT ... name = 1 阻塞
- 事務 B INSERT ... name = -100 阻塞
- 事務 B INSERT ... name = 2 成功
- 事務 B INSERT ... name = 3 成功
Round 2:
- 事務 A SELECT ... WHERE name = 2 FOR UPDATE;
- 對 [1, 10) 增加間隙鎖
- 事務 B INSERT ... name = 1 阻塞
- 事務 B INSERT ... name = 9 阻塞
- 事務 B INSERT ... name = 10 成功
- 事務 B INSERT ... name = 0 成功
Round 3:
- 事務 A SELECT ... WHERE name <= 2 FOR UPDATE;
- 對 (-∞, +∞) 增加間隙鎖
- 事務 B INSERT ... name = 3 阻塞
- 事務 B INSERT ... name = 300 阻塞
- 事務 B INSERT ... name = -300 阻塞
InnoDB 鎖機制總結
參考資料
- 《MySQL 技術內幕 InnoDB 儲存引擎》第二版 姜承堯著
- About MySQL InnoDB's Lock
寫在最後
這是一個不定時更新的、披著程式設計師外衣的文青小號,歡迎關注。