本文将介紹MySQL中的共享鎖(Shared Lock)和排他(tā)鎖(Exclusive Lock),它們用于控制對數據庫對象(如(rú)表、行等)的并發訪問(wèn)。将從(cóng)兩類鎖的概念、特點、獲取方式以及用途一步步深入了解,并通過實例進行深度剖析。

基本概念

  • 共享鎖(Shared Lock):

MySQL中的共享鎖(Shared Lock),也稱爲讀(dú)鎖,是一種鎖機(jī)制,用于讀(dú)取共享資源,實現讀(dú)取操作(zuò)的并發性。當一個事(shì)務獲取了共享鎖後,其他(tā)事(shì)務也可(kě)以獲取相(xiàng)同數據的共享鎖,以便并發地進行讀(dú)取操作(zuò),但(dàn)是不能修改數據。

  • 排他(tā)鎖(Exclusive Lock):

MySQL中的排他(tā)鎖(Exclusive Lock),也稱爲寫鎖,是一種鎖機(jī)制,用于确保對數據的獨占訪問(wèn),防止其他(tā)事(shì)務同時修改被鎖定的資源。持有排他(tā)鎖的事(shì)務會阻塞其他(tā)事(shì)務的讀(dú)取和修改操作(zuò)。

特點

  • 共享鎖(Shared Lock):
    • 多個事(shì)務可(kě)以同時持有共享鎖,用于讀(dú)取共享資源。
    • 共享鎖不阻塞其他(tā)事(shì)務獲取共享鎖,因此可(kě)以實現讀(dú)取操作(zuò)的并發性。
    • 共享鎖與共享鎖之間不會互相(xiàng)阻塞,可(kě)以同時存在多個共享鎖。
    • 共享鎖與排他(tā)鎖互斥,即當一個事(shì)務持有共享鎖時,其他(tā)事(shì)務無法獲取排他(tā)鎖。
  • 排他(tā)鎖(Exclusive Lock):
    • 一次隻能有一個事(shì)務獲取排他(tā)鎖,并且其他(tā)事(shì)務無法同時獲取相(xiàng)同數據的排他(tā)鎖。
    • 排他(tā)鎖可(kě)以用于修改數據,保證在事(shì)務進行修改時,其他(tā)事(shì)務無法對同一數據進行讀(dú)取或修改。
    • 排他(tā)鎖與共享鎖互斥,即當一個事(shì)務持有排他(tā)鎖時,其他(tā)事(shì)務無法獲取共享鎖或排他(tā)鎖。

獲取方式

  • 共享鎖(Shared Lock):通過LOCK IN SHARE MODE獲取共享鎖
SELECT * FROM table_name LOCK IN SHARE MODE;
  • 排他(tā)鎖(Exclusive Lock):通過FOR UPDATE獲取排他(tā)鎖
SELECT * FROM table_name FOR UPDATE;

用途

  • 共享鎖(Shared Lock):
    • 讀(dú)取一緻性:保證多個事(shì)務讀(dú)取相(xiàng)同數據時的一緻性。
    • 并發讀(dú)取:允許多個事(shì)務同時讀(dú)取相(xiàng)同的數據,提高并發性。
    • 防止髒讀(dú):防止事(shì)務讀(dú)取未提交的數據,确保數據的一緻性。
    • 讀(dú)取優先級控制:控制讀(dú)取操作(zuò)的優先級,确保重要的讀(dú)取操作(zuò)優先進行。
  • 排他(tā)鎖(Exclusive Lock):
    • 數據修改:确保在一個事(shì)務修改數據時,其他(tā)事(shì)務無法讀(dú)取或修改相(xiàng)同的數據,保證數據的獨占性和一緻性。
    • 數據完整性:防止并發修改導緻數據的損壞或沖突,保護數據的完整性。
    • 事(shì)務隔離(lí):在某些事(shì)務隔離(lí)級别下,如(rú)可(kě)重複讀(dú)或串行化,MySQL會自(zì)動爲寫操作(zuò)獲取排他(tā)鎖,保證事(shì)務之間的隔離(lí)性。
    • 數據一緻性維護:維護數據的一緻性,确保在對多個數據項進行修改時,其他(tā)事(shì)務無法讀(dú)取或修改相(xiàng)關的數據項。

實例演示

接下來(lái)我們通過實例演示如(rú)何使用共享鎖、排他(tā)鎖。

首先先創建一個my_test表,裡(lǐ)面包含主鍵(id)、名稱(name)、價格(price),并往表中插入幾條數據。

CREATE TABLE my_test (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  price DECIMAL(10, 2)
);

-- 插入數據
INSERT INTO my_test (id, name, price) VALUES
(1, 'Name A', 9.99),
(2, 'Name B', 19.99),
(3, 'Name C', 14.99);

此時我們開啓事(shì)務A,獲取一個共享鎖

-- 事(shì)務A
START TRANSACTION;
SELECT * FROM my_test WHERE id = 1 LOCK IN SHARE MODE;
-- 我們嘗試修改id=1的這條數據
UPDATE my_test SET price=19.99 WHERE id=1;
-- 休眠10秒,
SELECT SLEEP(10);
COMMIT;

我們再開啓事(shì)務B,同樣獲取一個共享鎖

-- 事(shì)務B
START TRANSACTION;
SELECT * FROM my_test WHERE id = 1 LOCK IN SHARE MODE;
-- 休眠20秒,
SELECT SLEEP(20);
-- 我們嘗試修改id=1的這條數據
UPDATE my_test SET price=29.99 WHERE id=1;
COMMIT;

與此同時,我們開啓事(shì)務C,嘗試獲取一個排他(tā)鎖

-- 事(shì)務C
START TRANSACTION;
SELECT * FROM my_test WHERE id = 1 FOR UPDATE;
COMMIT;

執行完上述語句我們得(de)到事(shì)務A、B、C的結果分(fēn)别如(rú)下:

mysql> -- 事(shì)務A
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM my_test WHERE id = 1 LOCK IN SHARE MODE;
+----+--------+-------+
| id | name   | price |
+----+--------+-------+
|  1 | Name A |  9.99 |
+----+--------+-------+
1 row in set (0.00 sec)

mysql> -- 我們嘗試修改id=1的這條數據
mysql> UPDATE my_test SET price=19.99 WHERE id=1;
Query OK, 1 row affected (20.38 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> -- 休眠10秒,
mysql> SELECT SLEEP(10);
+-----------+
| SLEEP(10) |
+-----------+
|         0 |
+-----------+
1 row in set (10.00 sec)

mysql> COMMIT;
Query OK, 0 rows affected (0.02 sec)
mysql> -- 事(shì)務B
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT * FROM my_test WHERE id = 1 LOCK IN SHARE MODE;
+----+--------+-------+
| id | name   | price |
+----+--------+-------+
|  1 | Name A |  9.99 |
+----+--------+-------+
1 row in set (0.00 sec)

mysql> -- 休眠20秒,
mysql> SELECT SLEEP(20);
+-----------+
| SLEEP(20) |
+-----------+
|         0 |
+-----------+
1 row in set (20.00 sec)

mysql> -- 我們嘗試修改id=1的這條數據
mysql> UPDATE my_test SET price=29.99 WHERE id=1;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> -- 事(shì)務C
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM my_test WHERE id = 1 FOR UPDATE;
+----+--------+-------+
| id | name   | price |
+----+--------+-------+
|  1 | Name A | 19.99 |
+----+--------+-------+
1 row in set (31.28 sec)

mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)

在運行結果中可(kě)以看(kàn)到,事(shì)務B中嘗試修改id=1的這條數據,提示:嘗試鎖定時發現死鎖;嘗試重新啓動事(shì)務。

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

事(shì)務A中嘗試修改id=1的這條數據,阻塞20.38秒,直到事(shì)務B中重新啓動事(shì)務時,釋放(fàng)了該共享鎖後,才能正常修改數據。

mysql> -- 我們嘗試修改id=1的這條數據
mysql> UPDATE my_test SET price=19.99 WHERE id=1;
Query OK, 1 row affected (20.38 sec)
Rows matched: 1  Changed: 1  Warnings: 0

事(shì)務C中,嘗試獲取排他(tā)鎖時,阻塞31.28秒,直到事(shì)務A中提交事(shì)務,釋放(fàng)共享鎖後,才能獲取到排他(tā)鎖。并且讀(dú)到的數據是事(shì)務A中修改的數據,price的值已經變成19.99。

mysql> SELECT * FROM my_test WHERE id = 1 FOR UPDATE;
+----+--------+-------+
| id | name   | price |
+----+--------+-------+
|  1 | Name A | 19.99 |
+----+--------+-------+
1 row in set (31.28 sec)

因此,我們可(kě)以發現:

  • 當有其他(tā)事(shì)務(事(shì)務A)同時擁有該共享鎖,無法修改數據。
  • 如(rú)果隻有一個事(shì)務(事(shì)務A)拿到了共享鎖,則該事(shì)務可(kě)以對數據進行 UPDATE DETELE 等操作(zuò);
  • 排他(tā)鎖與共享鎖是互斥的,當有事(shì)務獲取了共享鎖後,其他(tā)事(shì)務嘗試獲取排他(tā)鎖時會阻塞,反之同理(lǐ)。

 

注意事(shì)項

  • 在使用MySQL的共享鎖時,需要注意以下幾點:
    • 鎖沖突:共享鎖和獨占鎖之間存在沖突,不要在需要寫入操作(zuò)時使用共享鎖,以避免鎖沖突和并發性能降低。
    • 鎖定範圍:共享鎖應該精确到需要保護的數據範圍,避免過度鎖定整個數據庫或表。
    • 鎖定順序:确定好獲取和釋放(fàng)鎖定的順序,以避免死鎖的發生(shēng)。
    • 鎖定超時:避免長時間持有鎖定,盡快(kuài)釋放(fàng)鎖定,并設置合适的超時時間。
    • 鎖定粒度:将共享鎖應用于需要保護的數據範圍,避免過度鎖定。
    • 适用場景:共享鎖适用于讀(dú)取操作(zuò),但(dàn)對于寫入操作(zuò)應使用獨占鎖來(lái)保證數據的完整性。

  • 在使用MySQL的排他(tā)鎖時,需要注意以下幾點:
    • 鎖沖突:排他(tā)鎖是獨占鎖,需要避免與其他(tā)事(shì)務的鎖沖突。
    • 鎖定範圍:精确指定需要保護的數據範圍,避免過度鎖定。
    • 鎖定順序:确定好獲取和釋放(fàng)鎖定的順序,避免死鎖的發生(shēng)。
    • 鎖定超時:避免長時間持有鎖定,并設置合适的超時時間。
    • 鎖定粒度:應用于需要保護的數據範圍,避免過度鎖定。
    • 數據完整性:用于保護寫入操作(zuò)的原子性和一緻性。