前言
InnoDB存儲引擎和MyISAM的其中有兩個很重要的區別:一個是事務,一個就是鎖機制不同。事務之前有介紹,有問題的去補課;鎖方面的不同是InnoDB引擎既有表鎖又有行鎖,表鎖的應用和MyISAM表鎖用法一樣,行鎖只有通過有索引的字段作爲條件檢索的時候,纔會使用行級鎖,反之則是表鎖。
一、隱式加鎖
創建表和測試數據
用戶表user中id爲主鍵索引,username爲普通索引,money字段爲普通字段。在非事務環境下,隱式加鎖的過程時間非常短,不便研究。下面的栗子都是在事務環境下
CREATE TABLE `user`(
id INT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(50),
`money` DECIMAL(7,2),
key username(username)
)ENGINE=innodb CHARSET=utf8;
INSERT INTO user(`username`,`money`) VALUES('喬峯',5);
INSERT INTO user(`username`,`money`) VALUES('風清揚',20);
1.行鎖
當我們對用戶表進行增刪改(insert、delete、update)的時候,如果有檢索條件中使用到了索引,MySQL服務器會自動給當前操作的表添加行鎖。
終端A
更新id爲3的記錄,id爲主鍵索引,所以會引發行級鎖。
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update user set money=money+5 where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
終端B
B端在執行update操作的時候,先是光標不停閃爍,且進入鎖等待狀態,後報鎖超時錯誤
mysql> update user set money=money+5 where id=1;
Query OK, 1 row affected (0.08 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update user set money=money+12 where id=2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
終端A
A端執行提交(commit),系統會自動釋放表鎖
mysql> commit;
Query OK, 0 rows affected (0.03 sec)
2.表鎖
如果有檢索條件(money字段)中沒有使用到了索引,MySQL服務器會自動給當前操作的表添加表鎖。
終端A
mysql> select * from user;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 1 | 喬峯 | 10.00 |
| 2 | 風清揚 | 37.00 |
+----+----------+-------+
3 rows in set (0.02 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update user set money=money+5 where money=93.00;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
終端B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update user set money=money+12 where id=2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update user set money=money+12 where id=1;
ERROR 1205
二、顯式加行鎖
加鎖語句:
SELECT 查詢語句 LOCK IN SHARE MODE | FOR UPDATE
我們只需在正常的查詢語句後面加LOCK IN SHARE MODE | FOR UPDATE就能實現添加行級讀鎖和行級排他鎖
1.意向鎖
意向鎖是InnoDB自動加的,不需用戶干預,對於UPDATE、DELETE、INSERT操作,InnoDB會自動給操作的數據加排他鎖。 我們在顯式加行鎖的時候,MySQL服務器會自動給操作表添加一個意向鎖。此意向鎖是隱式添加的,多個意向鎖之間不會產生衝突且相互兼容
2.鎖的兼容問題
- | 共享鎖 | 排他鎖 | 意向共享鎖 | 意向排他鎖 |
---|---|---|---|---|
共享鎖 | 兼容 | 衝突 | 兼容 | 衝突 |
排他鎖 | 衝突 | 衝突 | 衝突 | 衝突 |
2.1.共享鎖兼容
終端A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user lock in share mode;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 1 | 喬峯 | 10.00 |
| 2 | 風清揚 | 37.00 |
+----+----------+-------+
3 rows in set (0.00 sec)
終端B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user lock in share mode;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 1 | 喬峯 | 10.00 |
| 2 | 風清揚 | 37.00 |
+----+----------+-------+
3 rows in set (0.00 sec)
2.2.共享鎖和意向排他鎖衝突
終端A
mysql> select * from user where id=2 lock in share mode;
+----+----------+-------+
| id | username | money |
+----+----------+-------+
| 2 | 風清揚 | 20.00 |
+----+----------+-------+
1 row in set (0.00 sec)
mysql> update user set money=money+5 where id=2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
2.3.排他鎖與其他鎖都衝突
終端A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user where id=2 for update;
+----+----------+--------+
| id | username | money |
+----+----------+--------+
| 3 | 風清揚 | 37.00 |
+----+----------+--------+
1 row in set (0.00 sec)
終端B
mysql> select * from user where id=2 lock in share mode;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update user set money=money+5 where id=2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
三、間隙鎖
間隙鎖是針對一個範圍條件的檢索時,InnoDB會給符合條件的已有的數據記錄的索引項加鎖;對於鍵值在條件範圍內但是並不存在的記錄,叫做“間隙”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是間隙鎖
終端A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user where id > 1 for update;
+----+----------+--------+
| id | username | money |
+----+----------+--------+
| 3 | 風清揚 | 103.00 |
+----+----------+--------+
1 row in set (0.00 sec)
終端B
mysql> update user set money=money+5 where id=2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> INSERT INTO user(`username`,`money`) VALUES('jack',99);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction