在 mysql
中有事務四大基本特性,隔離級別,鎖類型,b+
樹等各種需要我們學習的知識,這裏簡單分享一下 mysql
中的鎖。
對於 mysql
樂觀鎖就不再贅述,通常是增加 last_ver
字段,通過 mvcc
實現的。
下面就講講 mysql
的悲觀鎖以及鎖的實現方式
使用 mysql
數據庫的都知道我們經常使用的數據庫引擎有 MyISAM
和 InnoDB
。
MyISAM
默認表類型,它是基於傳統的
ISAM
類型,它是存儲記錄和文件的標準方法。不是事務安全的,而且不支持外鍵,如果執行大量的select
,insert
時,MyISAM
比較適合。
InnoDB
支持事務安全的引擎,支持外鍵、行鎖、事務是他的最大特點。如果有大量的
update
和insert
,建議使用InnoDB
,特別是針對多個併發和QPS較高的情況。
環境
- mysql版本:5.7.21
- 隔離級別:REPEATABLE-READ(RR)
- 創建測試表
Create Table: CREATE TABLE `test` (
`id` int(11) NOT NULL,
`score` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
- 準備數據
mysql> select * from test;
+----+-------+
| id | score |
+----+-------+
| 2 | 1 |
| 3 | 2 |
| 5 | 3 |
| 4 | 4 |
| 1 | 111 |
+----+-------+
5 rows in set (0.00 sec)
共享鎖
共享鎖又稱讀鎖,即多個事務對同一數據共享一把鎖,都能訪問數據,但是隻能讀不能寫。
關鍵語句lock in share mode
以下所有的 session
隔離級別均爲RR(REPEATABLE-READ)
,下面不再贅述。
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
開啓 sessionA
:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test where id = 2 lock in share mode;
+----+-------+
| id | score |
+----+-------+
| 2 | 1 |
+----+-------+
1 row in set (0.00 sec)
開啓sessionB:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test where id = 2;
+----+-------+
| id | score |
+----+-------+
| 2 | 1 |
+----+-------+
1 row in set (0.00 sec)
mysql> select * from test where id = 2 lock in share mode;
+----+-------+
| id | score |
+----+-------+
| 2 | 1 |
+----+-------+
1 row in set (0.00 sec)
mysql> update test set score = 100 where id = 2 ;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update test set score = 100 where id = 2 lock in share mode;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock in share mode' at line 1
會發現當我們執行 select
語句時,成功返回結果。
select
語句我寫了兩種,一種是簡單查詢語句,一種是加了共享鎖的查詢語句。
此時發現一個事務對數據加了共享鎖以後,其它事務還能再對該事務加共享鎖查詢,但是不能加排它鎖。
當我們執行 update
語句時發生了錯誤。
update test set score = 100 where id = 2 ;
語句時出現獲得鎖超時。update test set score = 100 where id = 2 lock in share mode;
時出現了語法錯誤,原因是mysql
會自動對select insert update
語句添加排它鎖。
排它鎖
排它鎖又稱寫鎖,當對數據上了排它鎖之後,就不能再被其它事務加任何鎖,包括共享鎖和排它鎖,獲取排它鎖的事務可以讀取和修改數據
關鍵語句lock in share mode
開啓 sessionA
:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test where id = 2 for update;
+----+-------+
| id | score |
+----+-------+
| 2 | 1 |
+----+-------+
1 row in set (0.00 sec)
mysql>
對id
爲 2
的數據加上排它鎖。
開啓 sessionB
:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test where id = 2 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> select * from test where id = 2 lock in share mode;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> select * from test where id = 2;
+----+-------+
| id | score |
+----+-------+
| 2 | 1 |
+----+-------+
1 row in set (0.00 sec)
mysql>
此時我們發現由於 sessionA
已經對 id=2
的數據加上了排它鎖,此時我們再進行共享鎖/排它鎖的查詢是失敗的,但是對於簡單查詢(不加鎖)是成功的。
update / insert / delete 自動加鎖
當我們執行
update / insert / delete
語句時,mysql
會自動爲該語句加上排它鎖。
測試一個 update 語句,讀者可以自行測試更多
開啓 sessionA
:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> update test set score = 100 where id = 2;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
執行更新語句,不顯示加任何鎖
開啓 sessionB
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update test set score = 99 where id = 2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> select * from test where id = 2;
+----+-------+
| id | score |
+----+-------+
| 2 | 1 |
+----+-------+
1 row in set (0.00 sec)
mysql> select * from test where id = 2 lock in share mode;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql>
update test set score = 99 where id = 2;
不加任何鎖,發現獲取鎖超時select * from test where id = 2;
簡單查詢成功select * from test where id = 2 lock in share mode;
加共享鎖查詢失敗
證明mysql
會爲update / insert / delete
自動加排它鎖。
鎖的實現方式
-
Record Lock
:行鎖,單個行記錄上的鎖 -
Gap Lock
:間隙鎖,鎖定一個範圍,但不包括記錄本身。GAP
鎖的目的,是爲了防止同一事務的兩次當前讀,出現幻讀的情況 -
Next-Key Lock
:Record Lock + Gap Lock
,鎖定一個範圍,並且鎖定記錄本身。對於行的查詢,都是採用該方法,主要目的是解決幻讀的問題
在 RR
隔離級別下,唯一索引使用的是行鎖,非唯一索引使用的 是 Next-Key Lock
。既然 Next-Key Lock
包含了 Record Lock
和 Gap Lock
,那麼我們就瞭解下Next-Key Lock
。
mysql> show create table test \G;
*************************** 1. row ***************************
Table: test
Create Table: CREATE TABLE `test` (
`id` int(11) NOT NULL,
`score` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `score_ind` (`score`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
mysql> select * from test;
+----+-------+
| id | score |
+----+-------+
| 2 | 1 |
| 4 | 3 |
| 7 | 3 |
| 5 | 5 |
+----+-------+
4 rows in set (0.00 sec)
對於 test 表又兩個字段 id
和 score
。id
爲主鍵,score
爲輔助索引
表中數據如上所示
開啓 sessionA
:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test where score = 3 for update;
+----+-------+
| id | score |
+----+-------+
| 4 | 3 |
| 7 | 3 |
+----+-------+
2 rows in set (0.00 sec)
mysql>
當在事務中執行完 select * from test where id = 3 for update;
語句後,由於我們設置的隔離級別爲RR
,score
爲 3
的數據就被加上了 Next-Key Lock
鎖,即行鎖+間隙鎖。
此時數據
id | score |
---|---|
4 | 3 |
7 | 3 |
加上行鎖。那麼間隙鎖又加在哪裏呢?
考慮到B+樹的連續性,能夠插入score
爲 3
的數據只有在區間(1,3),(3,3) ,(3,5)
那麼這個意思就是 score
爲 1
或者 5
的數據無法插入了嗎?
下面開啓 sessionB
做測試
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test (score,id) values(1, 3);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into test (score,id) values(1, 1);
Query OK, 1 row affected (0.00 sec)
我們發現插入
id | score | status |
---|---|---|
3 | 1 | 失敗 |
1 | 1 | 成功 |
爲什麼呢?兩條 score
爲1的數據,一個成功一個失敗
繼續往下看
mysql> insert into test (score,id) values(5, 4);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into test (score,id) values(5, 6);
Query OK, 1 row affected (0.00 sec)
插入
id | score | status |
---|---|---|
4 | 5 | 失敗 |
6 | 5 | 成功 |
也是一條成功一條失敗。爲什麼呢?
細心的你肯定發現了規律,表中的數據爲
mysql> select * from test;
+----+-------+
| id | score |
+----+-------+
| 2 | 1 |
| 4 | 3 |
| 7 | 3 |
| 5 | 5 |
+----+-------+
當我們在 sessionA
中執行 select * from test where score = 3 for update;
語句時,行鎖+間隙鎖會把(score,id)
分別爲 [(1,2),(3,4)],[(3,4),(3,7)],[(3,7),(5,5)]
之間的數據全部鎖住。範圍之外的數據可以插入。
下面對剛剛 sessionB
的插入進行解釋
mysql> insert into test (score,id) values(1, 3);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
數據 (1,3)
在 [(1,2),(3,4)]
範圍之內無法插入
mysql> insert into test (score,id) values(1, 1);
Query OK, 1 row affected (0.00 sec)
數據(1,1) 在[(1,2),(3,4)]
的左邊可以插入
關於 score
爲 5
的插入就不再解釋,與這個同理
爲了方便讀者演示,在上面建表語句已經全部貼出。相信讀者只要仔細閱讀文章,並且動手去做,就能夠深刻的理解間隙鎖