關於 mysql 的共享鎖 排它鎖以及鎖的實現方式 行鎖 間隙鎖 Next-Key Lock

mysql 中有事務四大基本特性,隔離級別,鎖類型,b+ 樹等各種需要我們學習的知識,這裏簡單分享一下 mysql 中的鎖。
對於 mysql 樂觀鎖就不再贅述,通常是增加 last_ver 字段,通過 mvcc 實現的。
下面就講講 mysql 的悲觀鎖以及鎖的實現方式

使用 mysql 數據庫的都知道我們經常使用的數據庫引擎有 MyISAMInnoDB

  • MyISAM

默認表類型,它是基於傳統的 ISAM 類型,它是存儲記錄和文件的標準方法。不是事務安全的,而且不支持外鍵,如果執行大量的 selectinsert時, MyISAM 比較適合。

  • InnoDB

支持事務安全的引擎,支持外鍵、行鎖、事務是他的最大特點。如果有大量的updateinsert,建議使用 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>

id2 的數據加上排它鎖。

開啓 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 LockRecord Lock + Gap Lock,鎖定一個範圍,並且鎖定記錄本身。對於行的查詢,都是採用該方法,主要目的是解決幻讀的問題

RR 隔離級別下,唯一索引使用的是行鎖,非唯一索引使用的 是 Next-Key Lock 。既然 Next-Key Lock 包含了 Record LockGap 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 表又兩個字段 idscoreid 爲主鍵,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; 語句後,由於我們設置的隔離級別爲RRscore3 的數據就被加上了 Next-Key Lock 鎖,即行鎖+間隙鎖。

此時數據

id score
4 3
7 3

加上行鎖。那麼間隙鎖又加在哪裏呢?
考慮到B+樹的連續性,能夠插入score3 的數據只有在區間(1,3),(3,3) ,(3,5)

那麼這個意思就是 score1 或者 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)] 的左邊可以插入
關於 score5 的插入就不再解釋,與這個同理

爲了方便讀者演示,在上面建表語句已經全部貼出。相信讀者只要仔細閱讀文章,並且動手去做,就能夠深刻的理解間隙鎖

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