InnoDB鎖-共享鎖、排他鎖與意向鎖


1.鎖的分類

鎖(Locking)是數據庫在並發訪問時保證數據一致性和完整性的主要機制。之前MyISAM鎖章節已經講過鎖分類,而InnoDB鎖按照粒度分為鎖定整個表的表級鎖(table-level locking)和鎖定數據行的行級鎖(row-level locking)
●表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,並發度最低。
●行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,並發度最高。

1.1 InnoDB的行鎖概述

InnoDB可以通過SHOW STATUS命令來檢查InnoDB_row_lock狀態變量用作分析系統上的行鎖的爭奪情況:
SHOW STATUS LIKE 'innodb_row_lock%';

五個參數說明如下:
●Innodb_row_lock_current_waits:當前正在等待鎖的數量。
●Innodb_row_lock_time:從系統啟動到現在鎖定總時間長度。
●Innodb_row_lock_time_avg:每次等待所花平均時間。
●Innodb_row_lock_time_max:從系統啟動到現在等待最長的一次所花的時間長度。
●Innodb_row_lock_waits:系統啟動到現在總共等待的次數。
其中InnoDB_row_lock_waitsInnoDB_row_lock_time_avg的參數值比較高,那就說明鎖爭用情況比較嚴重,那就要注意下了!

2.共享鎖與排他鎖

InnoDB主要有以下兩種類型的行鎖:
●共享鎖(S):允許獲得該鎖的事務讀取數據行(讀鎖),同時允許其他事務獲得該數據行上的共享鎖,並且阻止其他事務獲得數據行上的排他鎖。
●排他鎖(X):允許獲得該鎖的事務更新或刪除數據行(寫鎖),同時阻止其他事務取得該數據行上的共享鎖和排他鎖。

鎖類型

共享鎖(S)

排他鎖(X)

共享鎖(S)

兼容

沖突

排他鎖(X)

沖突

沖突

共享鎖和共享鎖可以兼容,排他鎖和其它鎖都不兼容。例如,事務A獲取了一行數據的共享鎖,事務B可以立即獲得該數據行的共享鎖,也就是鎖兼容;但是此時事務B如果想獲得該數據行的排他鎖,則必須等待事務A釋放數據行上的共享鎖,此種情況存在鎖沖突。請看以下示例:

session_1

session_2

(1)先設置事務T1提交類型為事務非自動提交。

(1)先設置事務T2提交類型為事務非自動提交。

-- 事務提交類型:0.事務非自動提交,1.事務自動提交

SET AUTOCOMMIT=0;

-- 事務提交類型:0.事務非自動提交,1.事務自動提交

SET AUTOCOMMIT=0;

(2)獲取了商品品牌表數據行ID=1上的共享鎖。

(2獲取了商品品牌表數據行ID=1上的共享鎖。

-- 獲取ID=1品牌行共享鎖

MySQL [(none)]> BEGIN;

Query OK, 0 rows affected (0.00 sec)

 

MySQL [(none)]> SELECT * FROM goods.goods_brand WHERE ID=1 FOR SHARE;

+----+--------+

| ID | Name   |

+----+--------+

|  1 | 榮耀   |

+----+--------+

1 row in set (0.00 sec)

-- 獲取ID=1品牌行共享鎖

MySQL [(none)]> SELECT * FROM goods.goods_brand WHERE ID=1 FOR SHARE;

+----+--------+

| ID | Name   |

+----+--------+

|  1 | 榮耀   |

+----+--------+

1 row in set (0.00 sec)

 

(2獲取了商品品牌表數據行ID=1上的排他鎖。此時該命令會一直處於等待狀態並且最終超時。也就是說,共享鎖和排他鎖不兼容。

-- 獲取ID=1品牌行排他鎖

MySQL [(none)]> SELECT * FROM goods.goods_brand WHERE ID=1 FOR UPDATE;

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

(2)在當前會話提交或者回滾事務。

 

-- 提交事務

MySQL [(none)]> commit;

Query OK, 0 rows affected (0.00 sec)

 

(3再獲取了商品品牌表數據行ID=1上的排他鎖。

-- 獲取ID=1品牌行排他鎖

MySQL [(none)]> SELECT * FROM goods.goods_brand WHERE ID=1 FOR UPDATE;

+----+--------+

| ID | Name   |

+----+--------+

|  1 | 榮耀   |

+----+--------+

1 row in set (0.00 sec)

注意:我們在事務中使用select ... for share語句獲取了數據行ID= 1上的共享鎖;對於MySQL 8.0之前的版本,可以使用select ... lock in share mode命令。select ... for update語句是獲取了數據行ID=1上的排他鎖。

3.意向鎖

InnoDB除了支持行級鎖,還支持由MySQL服務層實現的表級鎖(lock tables...write在指定的表加上表級排他鎖)。當這兩種鎖同時存在時,可能導致沖突。例如,事務A獲取了表中一行數據的讀鎖;然后事務B申請該表的寫鎖(例如修改表的結構)。如果事務B加鎖成功,那么它就應該能修改表中的任意數據行,但是A持有的行鎖不允許修改鎖定的數據行。顯然數據庫需要避免這種問題,B的加鎖申請需要等待A釋放行鎖。
那么如何判斷事務B是否應該獲取表級鎖呢?首先需要看該表是否已經被其他事務加上了表級鎖,然后依次查看該表中的每一行是否已經被其他事務加上了行級鎖。這種方式需要遍歷整個表中的記錄,效率很低。所以為了解決這個問題,InnoDB引入意向鎖(Intention Locks):
●意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。
●意向排他鎖(IX):事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。
注意:這兩種意向鎖都是表鎖
這個時候,事務A必須先申請該表的意向共享鎖,成功后再申請數據行的行鎖。事務B申請表鎖時,數據庫查看該表是否已經被其他事務加上了表級鎖;如果發現該表上存在意向共享鎖,說明表中某些數據行上存在共享鎖,事務B申請的寫鎖會被阻塞。
因此,意向鎖是為了允許行鎖和表鎖能夠共存,從而實現多粒度鎖機制。以下是表鎖(表鎖與行鎖都有共享鎖跟排他鎖)跟意向表鎖兼容性:

鎖類型

共享鎖(S)

排他鎖(X)

意向共享鎖(IS)

意向排他鎖(IX)

共享鎖(S)

兼容

沖突

兼容

沖突

排他鎖(X)

沖突

沖突

沖突

沖突

意向共享鎖(IS)

兼容

沖突

兼容

兼容

意向排他鎖(IX)

沖突

沖突

兼容

兼容

從表格可見表鎖跟意向鎖之間,只有共享鎖兼容,而意向鎖跟意向鎖之間是互相兼容的。
注意:InnoDB表存在兩種表級鎖,一種是lock tables語句手動指定的鎖,另一種是由InnoDB自動添加的意向鎖。對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖(X);對於普通SELECT語句,InnoDB不會加任何鎖。請看以下兩個示例。
示例1:

session_1

session_2

1)先設置事務T1提交類型為事務非自動提交。

1)先設置事務T2提交類型為事務非自動提交。

-- 事務提交類型:0.事務非自動提交,1.事務自動提交

SET AUTOCOMMIT=0;

-- 事務提交類型:0.事務非自動提交,1.事務自動提交

SET AUTOCOMMIT=0;

2)在當前事務T1中為商品品牌表goods_brand中的數據行ID=1加上排他鎖,同時會為表goods_brand加上意向排他鎖。

 

 

-- 獲取ID=1的品牌行排他鎖

MySQL [(none)]> BEGIN;

Query OK, 0 rows affected (0.00 sec)

 

MySQL [(none)]> SELECT * FROM goods.goods_brand WHERE ID=1 FOR UPDATE;

+----+--------+

| ID | Name   |

+----+--------+

|  1 | 榮耀   |

+----+--------+

1 row in set (14.63 sec)

 

 

2)在當前事務T2中顯式加表級共享鎖或者排他鎖,因為意向排他鎖和表級共享鎖沖突,所以session_2事務T2一直等待session_1事務T1釋放鎖。

-- 顯式加表級共享鎖或者排他鎖

LOCK TABLES goods.goods_brand READ;

-- 或者

-- LOCK TABLES goods.goods_brand WRITE;

阻塞...

3)T1提交或者回滾事務。

3)當session_1事務T1提交或者回滾事務釋放鎖,T2自動獲取goods_brand表的共享鎖。

-- 回滾

MySQL [(none)]> ROLLBACK;

Query OK, 0 rows affected (0.00 sec)

MySQL [(none)]> LOCK TABLES goods.goods_brand READ;

Query OK, 0 rows affected (8.09 sec)

 

 

4)釋放goods_brand表共享鎖。

-- 釋放共享鎖

MySQL [(none)]> UNLOCK TABLES;

Query OK, 0 rows affected (0.00 sec)

由此可驗證,意向排他鎖(IX)跟表級共享鎖(S)正如表格中所示是沖突的,其他類型鎖兼容這里就不一一示范了,大家可自行驗證。
示例2:

session_1

session_2

1)先設置事務T1提交類型為事務非自動提交。

1)先設置事務T2提交類型為事務非自動提交。

-- 事務提交類型:0.事務非自動提交,1.事務自動提交

SET AUTOCOMMIT=0;

-- 事務提交類型:0.事務非自動提交,1.事務自動提交

SET AUTOCOMMIT=0;

2)在當前事務T1中為商品品牌表goods_brand加上了意向排他鎖和數據行ID=1上的排他鎖。

2)在當前事務T2中為商品品牌表goods_brand加上了意向排他鎖和數據行ID=2上的排他鎖。

-- 獲取ID=1的品牌行排他鎖

MySQL [(none)]> BEGIN;

Query OK, 0 rows affected (0.00 sec)

 

MySQL [(none)]> SELECT * FROM goods.goods_brand WHERE ID=1 FOR UPDATE;

+----+--------+

| ID | Name   |

+----+--------+

|  1 | 榮耀   |

+----+--------+

1 row in set (0.00 sec)

-- 獲取ID=2的品牌行排他鎖

MySQL [(none)]> SELECT * FROM goods.goods_brand WHERE ID=2 FOR UPDATE;

+----+--------+

| ID | Name   |

+----+--------+

|  2 | 蘋果   |

+----+--------+

1 row in set (0.00 sec)

3)提交或者回滾事務。

 

 

-- 提交事務

MySQL [(none)]> COMMIT;

Query OK, 0 rows affected (0.00 sec)

由上述示例可以知道,事務T1和T2同時獲得了商品品牌表goods_brand上的意向排他鎖,以及不同數據行上的行級排他鎖,而且這兩種意向鎖並沒有發生沖突,由此可見意向鎖跟意向鎖之間是互相兼容的。InnoDB通過行級鎖,實現了更細粒度的控制,能夠支持更高的並發更新和查詢。還有一點是InnoDB行鎖是通過給索引上的索引項加鎖來實現的,當有明確指定的主鍵或者索引時候,才是行級鎖,否則就是表級鎖!在下一個章節,我將會介紹這個知識點。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM