切換語言為:簡體

盤點 MySQL 中那些常見的鎖

  • 爱糖宝
  • 2024-07-17
  • 2062
  • 0
  • 0

樂觀鎖

樂觀鎖是一種併發控制機制,它假設在事務提交之前沒有其他事務會修改相同的資料。對資料的操作是保持樂觀態度的,因此被稱為樂觀鎖。樂觀鎖透過記錄版本號或者時間戳來判斷資料是否被修改。回顧以前學過的知識點,在Java的CAS操作機制中也用到了樂觀鎖的思想來保證資料的可靠性。

那樂觀鎖的思想如何運用到資料庫中呢,我們透過栗子來學習樂觀鎖。比如在我之前的電商網站課設中,使用products表來管理商品庫存。每個商品記錄有一個version欄位用於樂觀鎖控制。

-- 初始庫存,爲了方便理解新增幾條資料  
INSERT INTO products (product_id, stock, version) VALUES (1, 100, 0);  
  
-- 事務A  
START TRANSACTION;  
  
-- 讀取商品庫存和版本號  
SELECT stock, version FROM products WHERE product_id = 1;  
-- 假設此時讀取到 stock=100, version=0  
  
-- 稍後進行庫存減少操作(此時其他事務未修改)  
UPDATE products  
SET stock = stock - 1, version = version + 1  
WHERE product_id = 1 AND version = 0;  
  
-- 檢查是否更新成功  
IF ROW_COUNT() = 1 THEN  
    COMMIT;  
ELSE  
    ROLLBACK;  
END IF;  
  
-- 事務B(併發執行)  
START TRANSACTION;  
  
-- 讀取商品庫存和版本號(在同一時間,也讀取到 stock=100, version=0)  
SELECT stock, version FROM products WHERE product_id = 1;  
  
-- 嘗試減少庫存(但此時version已不匹配,因為事務A它已經提交了)  
UPDATE products  
SET stock = stock - 1, version = version + 1  
WHERE product_id = 1 AND version = 0;  
  
-- 由於version不匹配,更新失敗,事務B回滾  
IF ROW_COUNT() = 0 THEN  
    ROLLBACK;  
END IF;

因此,從這個小栗子中可以看出,樂觀鎖通常是透過記錄版本號或者時間戳來判斷資料是否被修改的。

悲觀鎖

顧名思義,悲觀鎖在資料是否被修改上對資料持有的態度就並不那麼樂觀了。悲觀鎖假設在事務期間會發生衝突,它在操作期間持有鎖來避免衝突,和樂觀鎖恰恰相反。

我們往訂單處理系統中新增幾條資料,使用orders表來管理訂單狀態。

-- 初始訂單狀態  
INSERT INTO orders (order_id, status) VALUES (1, 'pending');  
  
-- 事務A  
START TRANSACTION;  
  
-- 查詢訂單狀態並加鎖 這裏的FOR UPDATE表示枷鎖
SELECT order_id, status FROM orders WHERE order_id = 1 FOR UPDATE;  
  
-- 執行一些業務邏輯,如檢查庫存、支付驗證等 裡略過
  
-- 更新訂單狀態為處理中  
UPDATE orders SET status = 'processing' WHERE order_id = 1;  
  
COMMIT;  

-- 如果在事務A執行期間,事務B嘗試更新同一訂單的狀態,它需要等待事務A提交或回滾後才能繼續。

由此可見,悲觀鎖的實現方式是透過SQL語句中的SELECT ... FOR UPDATE(新增獨佔鎖)或LOCK IN SHARE MODE(共享鎖)來加鎖。

行級鎖

行鎖的定義:行級鎖是對單個行加鎖,確保資料行的獨佔訪問

行鎖和悲觀鎖的獨佔鎖有著異曲同工之妙,因為SELECT ... FOR UPDATE在InnoDB中實際上是透過行級鎖來實現的。

表級鎖

定義:表級鎖是對整個表加鎖,其他連線無法修改或讀取此表的資料。在InnoDB中主要用於後設資料操作。

雖然InnoDB主要使用行級鎖,但在執行如ALTER TABLE這樣的DDL操作時,會隱式地對錶加鎖

-- 假設需要修改orders表的結構  
ALTER TABLE orders ADD COLUMN delivery_date DATE;  
-- 在執行此操作時,orders表被鎖定,其他事務無法訪問。

意向鎖

意向鎖是表級鎖的一種,它主要用於表示事務將來對錶中的行加鎖的意向

意向鎖是內部機制,通常情況下是不需要使用者直接操作的。它們在InnoDB內部用於協調行級鎖和表級鎖之間的衝突。

間隙鎖

間隙鎖是鎖定一個範圍的鍵,但不包括這些鍵的實際值,用於防止幻讀。我們經常背的八股就是可重複度的隔離級別下...請往下看👇👇👇

在可重複讀隔離級別下,InnoDB會自動使用間隙鎖來防止幻讀。

我們使用一個簡單略懂的栗子來解釋間隙鎖:

-- test_table表有一個自增主鍵id,當前最大值為5  
START TRANSACTION;  
  
-- 鎖定id大於5的所有記錄之間的間隙(任何將來可能插入的id值)  
SELECT * FROM test_table WHERE id > 5 FOR UPDATE;  
  
-- 此時,如果另一個事務嘗試插入id=6的記錄,它將被阻塞,直到當前事務提交或回滾。  
  
-- 提交事務  
COMMIT;

test_table中雖然還沒有id > 5的記錄,但SELECT ... FOR UPDATE查詢會鎖定所有大於5的id值之間的間隙,防止其他事務在這些位置插入新記錄。

Next-Key Locks

Next-Key Locks是行鎖和間隙鎖的組合,鎖定一個索引記錄以及該記錄之前的間隙

在InnoDB的預設隔離級別(可重複讀)中,當使用SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE時,InnoDB不僅會對涉及的索引記錄加行鎖,而且還會對索引記錄之前的間隙加間隙鎖,形成所謂的Next-Key Locks。這個鎖確保了索引記錄被鎖定,並且任何嘗試插入或修改被鎖定的索引記錄之前間隙的記錄都會被阻塞。

比方說我們有一個employees表,表裏有一個id列作為主鍵,當前表中的資料如下圖:

+----+-----------+  
| id | name      |  
+----+-----------+  
|  1 | Alice     |  
|  3 | Charlie   |  
|  5 | Eve       |  
+----+-----------+

事務A

START TRANSACTION;  
  
-- 鎖定id=3的記錄及其之前的間隙(防止插入id=2,但允許插入id>3且id<5的值)  
-- 同時也鎖定id=3之後的間隙(防止插入id=3的重複值,但這不是Next-Key Locks的直接作用,而是行鎖的效果)  
SELECT * FROM employees WHERE id = 3 FOR UPDATE;   
COMMIT;

事務B(需要併發執行) 在事務A執行期間,事務B嘗試執行以下操作:

-- 嘗試插入id=2的記錄(被阻塞,因為事務A的Next-Key Locks鎖定了id=2的間隙)  
INSERT INTO employees (id, name) VALUES (2, 'Bob'); -- 等待或者失敗 
  
-- 嘗試插入id=4的記錄(成功,因為id=4的間隙沒有被事務A的Next-Key Locks直接鎖定)  
-- 但是需要注意,如果事務A之後嘗試鎖定id=4或其間隙(透過另一個查詢),則這個插入可能會受到影響  
INSERT INTO employees (id, name) VALUES (4, 'David'); -- 可能成功,但是取決於事務A的後續操作  
  
-- 嘗試更新id=3的記錄(被阻塞,因為事務A對id=3的行加了行鎖)  
UPDATE employees SET name = 'Charlie Brown' WHERE id = 3; -- 等待事務A提交或回滾

在事務A裡,透過SELECT * FROM employees WHERE id = 3 FOR UPDATE;,InnoDB不僅鎖定了id=3的行,還鎖定了id=2到id=3之間的間隙(防止插入id=2),以及id=3之後的微小間隙(主要是爲了防止幻讀,儘管這個間隙在實際操作中通常不會由使用者直接插入資料來觸發阻塞,因為id=3已經是存在的記錄)。

在事務B中,嘗試插入id=2的記錄會被阻塞,因為它試圖在事務A鎖定的間隙中插入資料。

嘗試插入id=4的記錄可能成功,因為id=4的間隙沒有被事務A直接鎖定。但是,如果事務A之後執行了影響id=4或其間隙的操作(如另一個SELECT ... FOR UPDATE查詢),則事務B的插入可能會受到影響。

嘗試更新id=3的記錄會被阻塞,因為事務A已經對該行加了行鎖。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.