InnoDB行鎖機(jī)制詳解:記錄鎖、間隙鎖、臨鍵鎖與意向鎖

2024-12-17 15:12 更新

InnoDB 的行鎖實(shí)現(xiàn)主要基于索引,并通過多種類型的鎖來確保數(shù)據(jù)的一致性和并發(fā)控制。以下是InnoDB行鎖實(shí)現(xiàn)的幾個(gè)關(guān)鍵點(diǎn):

  1. 記錄鎖(Record Locks):這種鎖直接鎖定某行記錄的索引記錄。它通常用于唯一索引或主鍵索引上,當(dāng)使用精確匹配的查詢條件(如id = 1)時(shí),會(huì)使用記錄鎖。如果查詢條件不使用索引或使用非精確匹配條件,則可能退化為臨鍵鎖 。

  1. 間隙鎖(Gap Locks):間隙鎖鎖定一段范圍內(nèi)的索引記錄,但不包括記錄本身。這種鎖基于非唯一索引,并且是Next-Key Locking算法的一部分。間隙鎖可以阻止其他事務(wù)在鎖定的間隙中插入新的記錄 。

  1. 臨鍵鎖(Next-Key Locks):這是InnoDB中的一種特殊鎖,結(jié)合了記錄鎖和間隙鎖的特性。每個(gè)數(shù)據(jù)行上的非唯一索引列上都可能存在臨鍵鎖,它鎖定一個(gè)左開右閉的索引區(qū)間,從而解決幻讀問題 。

  1. 意向鎖(Intention Locks):InnoDB使用意向鎖來支持行級(jí)鎖和表級(jí)鎖的共存。意向鎖包括意向共享鎖(IS)和意向排他鎖(IX),它們在事務(wù)需要在更高層次上加鎖時(shí)自動(dòng)添加,以便與行級(jí)鎖兼容 。

下面 V 哥來跟據(jù)業(yè)務(wù)場景,舉例說明4種行鎖的使用。

1. 記錄鎖(Record Locks)

記錄鎖(Record Locks)是InnoDB中用于鎖定特定數(shù)據(jù)行的鎖。以下是一些具體的業(yè)務(wù)場景和示例,說明記錄鎖的使用方法和效果:

場景一:更新操作

當(dāng)需要更新某行數(shù)據(jù)時(shí),通常會(huì)使用記錄鎖來確保在更新過程中數(shù)據(jù)不會(huì)被其他事務(wù)修改。

示例: 假設(shè)有一個(gè)訂單表orders,其中包含訂單ID、訂單狀態(tài)等字段。當(dāng)一個(gè)訂單的狀態(tài)需要從“待發(fā)貨”更新為“已發(fā)貨”時(shí),可以使用以下SQL語句:

UPDATE orders SET status = '已發(fā)貨' WHERE order_id = 1;

在這個(gè)例子中,InnoDB會(huì)在order_id索引上加一個(gè)記錄鎖,鎖定訂單ID為1的行。其他事務(wù)在該行數(shù)據(jù)被解鎖之前,不能對(duì)其進(jìn)行修改。

場景二:查詢并鎖定

在某些情況下,需要先查詢某行數(shù)據(jù),然后對(duì)其進(jìn)行操作。這時(shí)可以使用SELECT ... FOR UPDATE語句來鎖定查詢結(jié)果中的行。

示例: 假設(shè)需要查詢某個(gè)用戶的詳細(xì)信息,并在查詢后更新其資料??梢允褂靡韵抡Z句:

SELECT * FROM users WHERE user_id = 1 FOR UPDATE;

這條語句不僅會(huì)返回用戶ID為1的記錄,還會(huì)在user_id索引上加一個(gè)記錄鎖。其他事務(wù)在該行數(shù)據(jù)被解鎖之前,不能對(duì)其進(jìn)行修改或查詢。

場景三:防止數(shù)據(jù)被其他事務(wù)修改

在某些業(yè)務(wù)邏輯中,可能需要確保某行數(shù)據(jù)在一段時(shí)間內(nèi)不會(huì)被其他事務(wù)修改。

示例: 假設(shè)有一個(gè)庫存表inventory,需要確保在計(jì)算庫存的過程中數(shù)據(jù)不會(huì)被其他事務(wù)修改。

SELECT * FROM inventory WHERE product_id = 100 FOR UPDATE;

這條語句會(huì)鎖定產(chǎn)品ID為100的庫存記錄,直到當(dāng)前事務(wù)結(jié)束。在此期間,其他事務(wù)不能修改該記錄。

場景四:避免數(shù)據(jù)重復(fù)插入

在插入數(shù)據(jù)時(shí),如果需要避免插入重復(fù)的數(shù)據(jù),可以使用記錄鎖來確保唯一性。

示例: 假設(shè)有一個(gè)用戶表users,需要插入一個(gè)新的用戶記錄,但要確保用戶名是唯一的。

INSERT INTO users (username, email) VALUES ('newuser', 'newuser@example.com') ON DUPLICATE KEY UPDATE email = 'newuser@example.com';

如果username字段是唯一索引,這條語句會(huì)首先嘗試插入新記錄。如果用戶名已存在,InnoDB會(huì)在username索引上加一個(gè)記錄鎖,并更新現(xiàn)有記錄的電子郵件地址。

場景五:事務(wù)中的一致性讀取

在事務(wù)中,如果需要確保讀取的數(shù)據(jù)在事務(wù)執(zhí)行期間不被其他事務(wù)修改,可以使用記錄鎖來實(shí)現(xiàn)。

示例: 假設(shè)在一個(gè)事務(wù)中需要讀取并處理某個(gè)訂單的所有相關(guān)信息。

START TRANSACTION;


SELECT * FROM orders WHERE order_id = 10 FOR UPDATE;


-- 執(zhí)行一些業(yè)務(wù)邏輯處理


COMMIT;

在這個(gè)事務(wù)中,SELECT ... FOR UPDATE語句會(huì)鎖定訂單ID為10的記錄,確保在事務(wù)執(zhí)行期間其他事務(wù)不能修改該記錄。

通過這些示例,可以看到記錄鎖在確保數(shù)據(jù)一致性和防止數(shù)據(jù)被并發(fā)事務(wù)修改方面的重要性。

2. 間隙鎖(Gap Locks)

間隙鎖(Gap Locks)在InnoDB中用于鎖定一個(gè)范圍內(nèi)的記錄,但不包括記錄本身。這種鎖主要用于防止其他事務(wù)在這個(gè)范圍內(nèi)插入新的記錄,從而維護(hù)數(shù)據(jù)的一致性和順序。以下是一些具體的業(yè)務(wù)場景和示例,說明間隙鎖的使用方法和效果:

場景一:防止數(shù)據(jù)插入

在某些業(yè)務(wù)邏輯中,可能需要確保某個(gè)范圍內(nèi)的數(shù)據(jù)不會(huì)被其他事務(wù)插入,以維護(hù)數(shù)據(jù)的完整性。

示例: 假設(shè)有一個(gè)員工表employees,包含員工ID和部門ID。如果需要防止在某個(gè)部門ID范圍內(nèi)插入新的員工記錄,可以使用以下SQL語句:

SELECT * FROM employees WHERE department_id BETWEEN 10 AND 20 FOR UPDATE;

這條語句會(huì)鎖定部門ID在10到20之間的所有記錄,但不包括這些記錄本身。其他事務(wù)在該范圍內(nèi)不能插入新的員工記錄,直到當(dāng)前事務(wù)結(jié)束。

場景二:范圍查詢并鎖定

在進(jìn)行范圍查詢時(shí),如果需要確保查詢結(jié)果中的記錄不會(huì)被其他事務(wù)修改,可以使用間隙鎖。

示例: 假設(shè)需要查詢某個(gè)日期范圍內(nèi)的所有訂單,并鎖定這些訂單記錄。

SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31' FOR UPDATE;

這條語句會(huì)鎖定所有訂單日期在2024年1月1日到1月31日之間的訂單記錄。其他事務(wù)在當(dāng)前事務(wù)結(jié)束之前,不能修改這些訂單記錄。

場景三:避免數(shù)據(jù)重復(fù)

在插入數(shù)據(jù)時(shí),如果需要避免在某個(gè)范圍內(nèi)插入重復(fù)的數(shù)據(jù),可以使用間隙鎖來確保唯一性。

示例: 假設(shè)有一個(gè)產(chǎn)品表products,需要確保在某個(gè)價(jià)格范圍內(nèi)不會(huì)插入重復(fù)的產(chǎn)品。

SELECT * FROM products WHERE price BETWEEN 100 AND 200 FOR UPDATE;

這條語句會(huì)鎖定價(jià)格在100到200之間的所有產(chǎn)品記錄,但不包括這些記錄本身。其他事務(wù)在當(dāng)前事務(wù)結(jié)束之前,不能在這個(gè)價(jià)格范圍內(nèi)插入新的產(chǎn)品記錄。

場景四:維護(hù)數(shù)據(jù)順序

在某些業(yè)務(wù)邏輯中,可能需要確保數(shù)據(jù)的插入順序,間隙鎖可以用于維護(hù)這種順序。

示例: 假設(shè)有一個(gè)任務(wù)表tasks,需要確保任務(wù)的插入順序按照任務(wù)的優(yōu)先級(jí)進(jìn)行。

SELECT * FROM tasks WHERE priority BETWEEN 1 AND 5 FOR UPDATE;

這條語句會(huì)鎖定優(yōu)先級(jí)在1到5之間的所有任務(wù)記錄,但不包括這些記錄本身。其他事務(wù)在當(dāng)前事務(wù)結(jié)束之前,不能在這個(gè)優(yōu)先級(jí)范圍內(nèi)插入新的任務(wù)記錄。

場景五:防止數(shù)據(jù)覆蓋

在某些情況下,可能需要防止在某個(gè)范圍內(nèi)的數(shù)據(jù)被其他事務(wù)覆蓋。

示例: 假設(shè)有一個(gè)庫存表inventory,需要確保在某個(gè)庫存量范圍內(nèi)的數(shù)據(jù)不會(huì)被其他事務(wù)覆蓋。

SELECT * FROM inventory WHERE stock_level BETWEEN 50 AND 100 FOR UPDATE;

這條語句會(huì)鎖定庫存量在50到100之間的所有庫存記錄,但不包括這些記錄本身。其他事務(wù)在當(dāng)前事務(wù)結(jié)束之前,不能在這個(gè)庫存量范圍內(nèi)插入或修改庫存記錄。

通過這些示例,可以看到間隙鎖在防止數(shù)據(jù)被并發(fā)事務(wù)插入和維護(hù)數(shù)據(jù)一致性方面的重要性。

3. 臨鍵鎖(Next-Key Locks)

臨鍵鎖(Next-Key Locks)是InnoDB中一種特殊的鎖,它結(jié)合了記錄鎖和間隙鎖的特點(diǎn),用于鎖定一個(gè)記錄及其后繼記錄之間的“間隙”。這種鎖主要用于解決幻讀問題,確保在可重復(fù)讀(Repeatable Read)隔離級(jí)別下,事務(wù)可以看到一致的快照視圖。

以下是一些具體的業(yè)務(wù)場景和示例,說明臨鍵鎖的使用方法和效果:

場景一:防止幻讀

在可重復(fù)讀隔離級(jí)別下,如果一個(gè)事務(wù)需要多次讀取同一數(shù)據(jù)集,臨鍵鎖可以確保在事務(wù)執(zhí)行期間,其他事務(wù)不能在這些數(shù)據(jù)之間插入新的記錄。

示例: 假設(shè)有一個(gè)訂單表orders,包含訂單ID和訂單狀態(tài)。一個(gè)事務(wù)需要多次檢查某個(gè)訂單的狀態(tài),確保在處理期間訂單狀態(tài)沒有被其他事務(wù)修改。

START TRANSACTION;
SELECT * FROM orders WHERE order_id = 100 FOR UPDATE;
-- 檢查訂單狀態(tài)
-- 執(zhí)行一些業(yè)務(wù)邏輯
SELECT * FROM orders WHERE order_id = 100 FOR UPDATE;
COMMIT;

在這個(gè)例子中,第一次SELECT ... FOR UPDATE會(huì)鎖定訂單ID為100的記錄,同時(shí)也會(huì)鎖定該記錄后面的間隙,防止其他事務(wù)在這個(gè)間隙中插入新的訂單記錄。

場景二:范圍查詢并鎖定

在進(jìn)行范圍查詢時(shí),如果需要確保查詢結(jié)果中的記錄不會(huì)被其他事務(wù)插入或修改,可以使用臨鍵鎖。

示例: 假設(shè)需要查詢某個(gè)價(jià)格范圍內(nèi)的所有產(chǎn)品,并鎖定這些產(chǎn)品記錄。

SELECT * FROM products WHERE price BETWEEN 100 AND 200 FOR UPDATE;

這條語句會(huì)鎖定價(jià)格在100到200之間的所有產(chǎn)品記錄,同時(shí)也會(huì)鎖定這些記錄后面的間隙。其他事務(wù)在當(dāng)前事務(wù)結(jié)束之前,不能在這個(gè)價(jià)格范圍內(nèi)插入新的產(chǎn)品記錄,也不能修改這些記錄。

場景三:維護(hù)數(shù)據(jù)順序

在某些業(yè)務(wù)邏輯中,可能需要確保數(shù)據(jù)的插入順序,臨鍵鎖可以用于維護(hù)這種順序。

示例: 假設(shè)有一個(gè)任務(wù)表tasks,需要確保任務(wù)的插入順序按照任務(wù)的優(yōu)先級(jí)進(jìn)行。

SELECT * FROM tasks WHERE priority BETWEEN 1 AND 5 FOR UPDATE;

這條語句會(huì)鎖定優(yōu)先級(jí)在1到5之間的所有任務(wù)記錄,同時(shí)也會(huì)鎖定這些記錄后面的間隙。其他事務(wù)在當(dāng)前事務(wù)結(jié)束之前,不能在這個(gè)優(yōu)先級(jí)范圍內(nèi)插入新的任務(wù)記錄。

場景四:防止數(shù)據(jù)覆蓋

在某些情況下,可能需要防止在某個(gè)范圍內(nèi)的數(shù)據(jù)被其他事務(wù)覆蓋。

示例: 假設(shè)有一個(gè)庫存表inventory,需要確保在某個(gè)庫存量范圍內(nèi)的數(shù)據(jù)不會(huì)被其他事務(wù)覆蓋。

SELECT * FROM inventory WHERE stock_level BETWEEN 50 AND 100 FOR UPDATE;

這條語句會(huì)鎖定庫存量在50到100之間的所有庫存記錄,同時(shí)也會(huì)鎖定這些記錄后面的間隙。其他事務(wù)在當(dāng)前事務(wù)結(jié)束之前,不能在這個(gè)庫存量范圍內(nèi)插入新的庫存記錄,也不能修改這些記錄。

場景五:數(shù)據(jù)一致性檢查

在某些業(yè)務(wù)邏輯中,可能需要在事務(wù)中多次檢查數(shù)據(jù)的一致性,臨鍵鎖可以確保在檢查期間數(shù)據(jù)不會(huì)被其他事務(wù)修改。

示例: 假設(shè)有一個(gè)員工表employees,需要在事務(wù)中多次檢查某個(gè)員工的薪資是否符合預(yù)期。

START TRANSACTION;
SELECT * FROM employees WHERE employee_id = 1 FOR UPDATE;
-- 檢查薪資
-- 執(zhí)行一些業(yè)務(wù)邏輯
SELECT * FROM employees WHERE employee_id = 1 FOR UPDATE;
COMMIT;

在這個(gè)例子中,SELECT ... FOR UPDATE會(huì)鎖定員工ID為1的記錄,同時(shí)也會(huì)鎖定該記錄后面的間隙,確保在事務(wù)執(zhí)行期間其他事務(wù)不能在這個(gè)間隙中插入新的員工記錄或修改該員工的薪資。

通過這些示例,可以看到臨鍵鎖在防止幻讀、維護(hù)數(shù)據(jù)一致性和順序方面的重要性。

4. 意向鎖(Intention Locks)

意向鎖(Intention Locks)是InnoDB存儲(chǔ)引擎中的一種內(nèi)部使用的鎖,用于表示事務(wù)將要請(qǐng)求的鎖類型,并幫助事務(wù)在不同級(jí)別的鎖(行鎖和表鎖)之間實(shí)現(xiàn)兼容性。意向鎖主要有以下兩種類型:

  1. 意向共享鎖(Intention Shared Lock,IS):事務(wù)在請(qǐng)求多個(gè)行的共享鎖之前,首先在表級(jí)別加上意向共享鎖。
  2. 意向排他鎖(Intention Exclusive Lock,IX):事務(wù)在請(qǐng)求多個(gè)行的排他鎖之前,首先在表級(jí)別加上意向排他鎖。

下面是業(yè)務(wù)場景和示例:

場景一:多行數(shù)據(jù)的更新

當(dāng)需要更新表中的多行數(shù)據(jù)時(shí),事務(wù)會(huì)在表級(jí)別加上意向排他鎖,以表明它打算在表中放置排他鎖。

示例: 假設(shè)有一個(gè)在線購物平臺(tái)的訂單表orders,需要批量更新多個(gè)訂單的狀態(tài)為“已發(fā)貨”。

START TRANSACTION;


UPDATE orders SET status = 'Shipped' WHERE order_id IN (101, 102, 103);


COMMIT;

在這個(gè)事務(wù)中,InnoDB會(huì)在orders表上自動(dòng)加上意向排他鎖(IX),然后在每條選定的訂單記錄上加上排他鎖(X)。這表明事務(wù)打算修改這些行,并且其他事務(wù)不能同時(shí)修改這些行或在表上加上共享鎖。

場景二:多行數(shù)據(jù)的讀取

如果一個(gè)查詢需要讀取多行數(shù)據(jù),并且事務(wù)需要確保這些數(shù)據(jù)在讀取期間不被修改,事務(wù)會(huì)在表級(jí)別加上意向共享鎖。

示例: 假設(shè)需要為報(bào)表生成讀取特定條件的訂單數(shù)據(jù),以確保在生成報(bào)表期間這些訂單數(shù)據(jù)不被修改。

START TRANSACTION;


SELECT * FROM orders WHERE customer_id = 100 FOR UPDATE;


COMMIT;

在這個(gè)事務(wù)中,InnoDB會(huì)在orders表上自動(dòng)加上意向排他鎖(IX),然后在滿足條件的每一行上加上排他鎖(X)。這確保了在事務(wù)期間,其他事務(wù)不能修改這些訂單記錄。

場景三:避免死鎖

在復(fù)雜的業(yè)務(wù)邏輯中,多個(gè)事務(wù)可能需要在不同的表或同一表的不同行上請(qǐng)求鎖。意向鎖有助于避免死鎖,因?yàn)樗试S事務(wù)在請(qǐng)求行鎖之前表明其鎖意圖。

示例: 假設(shè)有兩個(gè)事務(wù),事務(wù)A需要更新orders表和customers表,事務(wù)B也需要更新這兩個(gè)表,但順序相反。

事務(wù)A:

START TRANSACTION;


UPDATE orders SET ... WHERE order_id = 101;
UPDATE customers SET ... WHERE customer_id = 100;


COMMIT;

事務(wù)B:

START TRANSACTION;


UPDATE customers SET ... WHERE customer_id = 100;
UPDATE orders SET ... WHERE order_id = 101;


COMMIT;

即使兩個(gè)事務(wù)請(qǐng)求鎖的順序不同,意向鎖的存在可以確保它們在請(qǐng)求行鎖之前在表級(jí)別請(qǐng)求相應(yīng)的意向鎖,從而降低死鎖的風(fēng)險(xiǎn)。

場景四:表結(jié)構(gòu)變更時(shí)的兼容性

當(dāng)數(shù)據(jù)庫管理員需要對(duì)表結(jié)構(gòu)進(jìn)行變更,如添加索引,而表中已有行鎖時(shí),意向鎖提供了一種機(jī)制來確保結(jié)構(gòu)變更不會(huì)與現(xiàn)有的行級(jí)鎖沖突。

示例: 數(shù)據(jù)庫管理員需要為orders表添加一個(gè)新索引,但表中已有多個(gè)行被鎖。

ALTER TABLE orders ADD INDEX (new_column);

在這個(gè)操作中,InnoDB會(huì)在表級(jí)別檢查意向鎖,以確保沒有其他事務(wù)正在修改表中的數(shù)據(jù),從而安全地進(jìn)行索引的添加。

意向鎖是InnoDB內(nèi)部自動(dòng)處理的,不需要用戶手動(dòng)請(qǐng)求。它們在事務(wù)需要在多行上請(qǐng)求共享鎖或排他鎖時(shí),提供了一種高效的協(xié)調(diào)機(jī)制,以確保數(shù)據(jù)庫的并發(fā)控制和數(shù)據(jù)一致性。

5. 最后

快照讀取和讀取沖突檢測:InnoDB通過快照讀取確保事務(wù)讀取到的數(shù)據(jù)一致性,并通過讀取沖突檢測來處理并發(fā)事務(wù)中的沖突,確保數(shù)據(jù)的正確性和一致性 。

鎖的兼容性:InnoDB中的鎖有一套兼容性規(guī)則,共享鎖(S)和排他鎖(X)可以共存,但排他鎖會(huì)阻塞其他事務(wù)對(duì)同一資源的訪問。意向鎖是InnoDB自動(dòng)添加的,不需要用戶干預(yù) 。

行鎖的實(shí)現(xiàn)方式:InnoDB行鎖是通過給索引上的索引項(xiàng)加鎖實(shí)現(xiàn)的。如果查詢不通過索引條件,InnoDB將使用表鎖而不是行鎖,這可能會(huì)影響并發(fā)性能 。

鎖的優(yōu)化:合理使用索引,減少鎖的持有時(shí)間,避免死鎖等策略可以幫助優(yōu)化InnoDB行鎖的性能 。

總的來說,InnoDB的行鎖機(jī)制通過索引來實(shí)現(xiàn)對(duì)數(shù)據(jù)行的精確控制,并通過多種鎖類型和兼容性規(guī)則來處理并發(fā)事務(wù)中的沖突。開發(fā)者需要注意合理使用索引和優(yōu)化事務(wù)處理,以提高數(shù)據(jù)庫的并發(fā)性能和穩(wěn)定性。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)