MySQL中鎖的分類

程式猿集錦發表於2020-12-04

微信搜尋“coder-home”或掃一掃下面的二維碼,關注公眾號,第一時間瞭解更多幹貨分享,還有各類視訊教程資源。掃描它,帶走我
MySQL中鎖的分類


鎖的概念

在資料庫中,多個執行緒或事物併發的時候,難免會出現訪問到同一行資料資源的情況,為了避免這種情況下資源的競爭,資料庫資料邏輯的一致性,要合理的控制事務訪問的規則。而鎖就是用來控制這些訪問規則的重要工具。

鎖的分類

我們經常提到的鎖有很多,這裡簡單的羅列一下我們經常聽到的鎖的名稱:

全域性鎖、表鎖、後設資料鎖、行鎖、

讀鎖、寫鎖、共享鎖、排它鎖、S鎖、X鎖

意向鎖、意向共享鎖、意向排它鎖、IS鎖、IX鎖

記錄鎖、間隙鎖、臨鍵鎖、

自增鎖、樂觀鎖、悲觀鎖、空間鎖、位次鎖、死鎖

從不同的角度來對鎖進行劃可以有不同的分類,同一種鎖,可以屬於多種型別。我們下面從不同的角度來把他們劃分一下。


從鎖的粒度(範圍)劃分

從鎖對資料鎖定的粒度或者範圍的角度來看,鎖可以分為全域性鎖、表鎖、後設資料鎖、行鎖四種型別。它們對資料的鎖定範圍由大到底依次減小。

全域性鎖對資料鎖定的粒度比較粗、範圍比較大,資料庫的併發性最低。表鎖對資料鎖定的範圍相比全域性鎖而言,鎖定資料的粒度稍微細緻了一些,範圍也從全域性資料庫縮小到了某一個表的級別上,此時的資料庫併發效能提高了不少。但是相對於行鎖,還是略遜一籌。行鎖對資料的鎖定範圍精確定位到某一行資料,它對資料庫的併發效能起到了至關重要的作用,相對全域性鎖和表鎖有了質地飛躍。

而後設資料鎖,是一種特殊的鎖。他是為了鎖定表的結構而設計出來的,為了是保證在有資料查詢請求的時候,不能對錶結構進行DDL的操作,以此來保證資料查詢的時候和表結構的一致性。

全域性鎖

全域性鎖是對全域性資料庫而言的,這裡的全域性是指MySQL資料庫例項下面的所有資料庫。它可以把整個資料例項中的所有資料庫都鎖起來。避免其他任何事務對資料庫例項下面的任何資料庫有任何的操作。這裡強調一下:是避免發生“寫”的操作,言外之意,這個鎖是讓資料庫例項進入全域性只讀狀態。

如何啟用全域性鎖?

  1. 使用flush tables with read lock;命令開啟全域性鎖。

    注意:在會話正常exit之後,這個全域性鎖就會釋放。資料庫例項就會進入正常提供讀寫的狀態。如果因為客戶端異常關閉或斷開會話後,這個全域性鎖也會馬上釋放,資料庫也立即恢復正常讀寫狀態。

  2. 使用set global readonly=true;命令開啟全域性鎖。

    注意:如果會話異常退出或斷開連線後,此時的全域性鎖不會釋放,整個資料庫例項仍然處於只讀狀態。需要我們重新登入資料庫執行set global readonly=false;才可以釋放全域性鎖,資料庫才可以提供正常的讀寫功能。所以在使用這種方式的時候要格外的注意,執行這個命令的會話不要異常退出或關閉。

    另外,這個引數的值,還會用在MySQL主從同步的時候判斷某一個資料庫是從庫還是主庫,一般從庫的這個會設定為readonly=true,當前這個只對普通使用者有效,對超級使用者或者組從複製的使用者來說是不會受這個只讀引數的影響的。

全域性鎖的使用場景有哪些?

因為全域性鎖的鎖定資料範圍比較大,所有我們在一般情況下面很少使用到這種鎖。但是在一種場景下比較常見:備份資料庫的時候可能使用到。

之所以說可能使用到,是因為是在某些前提條件下才會使用,並不是說備份資料庫就一定要使用到這種全域性鎖。下面我們說一下在備份的時候為什麼會需要使用到這種全域性只讀的鎖。

現在我們要對一個資料庫使用mysqldump命令進行邏輯備份,裡面有很多表,而這些表在備份的時候,是按照順序一個一個被匯出來生成sql檔案的。拿資料庫備份期間,你正在購買一件商品這個場景舉例說明。

  • 在備份訂單表的時候,你購買商品的這個事務還沒提交,所以此時的訂單表中沒有你購買商品記錄,進而備份結果中的商品表就沒有你購買商品的這條記錄。
  • 但是在備份餘額表的時候,你購買商品的這個事務提交成功了,那麼此時會向訂單表插入購買商品的記錄,同時在餘額表中扣除你的餘額。這個時候備份的餘額表中有扣除你餘額的記錄。但是剛才備份的訂單表中卻沒有你購買的商品記錄。
  • 當我們使用這個備份後的sql檔案還原一個新的資料庫的時候,新庫的資料和原始庫中的資料就會有差別。訂單表中沒有你的商品記錄,但是餘額表去扣除了你的餘額。此時就發生了資料邏輯上的不一致。

上述備份後的資料結果之所以在業務邏輯上不一致的原因是在備份的過程中,仍然有業務在向一些表中寫入資料。所以,為了保證整個資料庫的資料在業務邏輯上的一致性,在備份的過程中,我們要保證沒有任何業務向資料庫表中寫入任何記錄,也就是說在備份期間,資料庫只能提供讀的服務,不能提供寫的服務。

或者在備份期間,資料庫提供讀寫服務也可以,但是從我備份資料庫開始的時刻起,其他的任何對資料庫修改對我這個備份的任務而言,都不可見。我只備份我的備份任務啟動那一刻的資料庫中的資料情況。而這個需求,正好對應了innodb儲存引擎中的MVCC功能的特點。

根據前面我們的分析,當使用innodb儲存引擎的表的時候,備份的時候可以讓資料庫提供讀寫功能,因為innodb支援事務,而在事務的可重複讀隔離級別下(也就是MySQL的innodb預設的事務隔離級別),會有MVCC的支援,在備份事務執行的過程中,事務所能看到的資料內容,始終和它剛啟動的時候看到的資料內容是一致的。這樣就可以避免我們在備份的過程中發生資料在邏輯上的不一致的問題。

如果你的資料庫中所有的表都是innodb儲存引擎型別的表,那麼在使用mysqldump命令進行資料庫備份的時候,是不需要使用這種全域性鎖的,只要在備份命令中指定--single-transaction引數既可,該引數的功能就是開啟一個一致性檢視,然後基於此檢視進行一致性讀的備份操作。

如果你的資料庫中還有在使用MyISAM儲存引擎的表,那麼此時就只能用全域性鎖讓資料庫進入全域性只讀狀態,然後再使用mysqldump命令進行備份工作,否則將會出現備份的資料結果在邏輯上的不一致的問題。

表鎖

相比全域性鎖而言,表鎖是對一個表加上鎖。這個鎖可以是共享鎖(也稱為S鎖、讀鎖)或者排它鎖(也稱為X鎖、寫鎖)。

  • 如果給表增加一個共享鎖,這個表還可以提供其他事務的讀的請求。
  • 如果給表增加一個排它鎖,那麼這個表不能提供其他事務任何請求,只能被給它增加排它鎖的事務獨享使用。

如何給表增加表鎖?

innodb的表增加表鎖的方式如下:

locks table t1 read, t2 write; -- 執行完成該語句後,其他事務可以讀t1,但是不能讀寫t2。
select * from t1; -- 只能查詢t1,但是不可以寫t1。
insert into t1(id) values (11); -- 執行失敗,不可以寫t1。
select * from t2; -- 可以讀t2,也可以寫t2。
insert into t2(id) values (22); -- 可以寫t2。
select * from t3; -- 這個語句不可以執行,因為t3它沒有包含在前面增加表鎖的宣告語句中。在執行unlock tables命令之前,只能操作t1和t2,除此之外不能訪問任何其他表。

如何釋放表鎖

如果想對某個表取消掉它的表鎖,只要在增加表鎖後最後使用如下命令就可以釋放表鎖。

unlock tables; ---- 釋放表鎖

另外,當前客戶端埠連線或異常關閉的時候,也會自動釋放表鎖。

當一個表被某個事務增加上讀鎖之後,這個表還可以被其他事務讀,但是不能被其他事務寫。與此同時,當前事務也只能讀取這個表,不能寫這個表,也不能讀取其他表。如果在當前事務中嘗試讀取其他表,或者寫這個表,則會有如下的錯誤產生。
在這裡插入圖片描述

當前一個表被某一個事務加上寫鎖之後,這個表不可以被其他時候讀,也不能被其他事務寫,只能被當前事務讀寫。與此同時,當前事務也只能讀寫這個表,對其他表也不能讀寫。如下所示:
在這裡插入圖片描述

後設資料鎖

在瞭解後設資料鎖之前,我們先介紹一個場景:假如你查詢了一個表中所有的資料,這個時候,你正在一行一行的讀取表中資料,如果這個時候,有其他人把這個表的結構給修改了,刪除了一列、或者增加了一列。這個時候你正在讀取資料?發生什麼?你讀取的資料和表的結構不一致了。

為了避免這樣的情況發生,MySQL中有一種鎖叫做後設資料鎖,而這種鎖的功能就是鎖住表結構,保證讀寫的正確性。避免表結構被直接修改而導致的表中的資料無法正常返回給查詢的客戶端。後設資料鎖的簡稱為MDL(Meta Data Lock)。

後設資料鎖也分為共享鎖(S鎖/讀鎖)和排它鎖(X鎖/寫鎖)兩種,與表鎖不同的是,後設資料鎖是MySQL自己維護的,不需要我們自己手動的增加或釋放。

  • 當我們對一個表中的資料進行增刪改查(DML)操作的時候,這個表會被自動增加上後設資料共享鎖,此時這個表還可以被其他人繼續進行增刪改查(DML)的操作。因為此時的MDL後設資料鎖的型別是共享鎖,可以和其他人共享被鎖住的資源。但是不能被增加後設資料排它鎖了,因為共享鎖和排它鎖是互斥的。
  • 當我們對一個表結構進行修改(DDL)操作的時候,這個表會被自動增加上後設資料排它鎖,此時這個表只能供當前事務來修改表結構。如果有其他事務要修改表結構,則需要等待前一個修改表結構的語句完成後,釋放了後設資料排它鎖之後,後面的事務才可以進行表結構的修改。因而為排它鎖和排它鎖是互斥的。

對錶進行增刪改查DML操作的時候,會對錶增加MDL讀鎖;對錶進行修改表結構DDL操作的時候,需要對錶增加MDL寫鎖。而MDL讀鎖和MDL讀鎖之間不互斥,MDL讀鎖和MDL寫鎖之間互斥,MDL寫鎖和MDL寫鎖之間也互斥。

後設資料鎖MDL的關係如下:

互斥關係mdl讀鎖mdl寫鎖
MDL讀鎖可以共存互斥
MDL寫鎖互斥互斥

所以說:當有一個事務正在對一個表進行增刪改成CURD操作的時候,這個表可以被其他事務同時進行CURD的操作,但是不能被其他事務進行修改表結構DDL的操作;當有一個事務對一個表正在執行修改表結構DDL操作的時候,這個表不能被其他事務做CURD的操作,也不能被其他事務做更改表結構DDL的操作。需要等待當前事務的修改表結構的操作結束後,才能開始後續的增刪改查CURD的操作或更改表結構DDL的操作。

因此,在生產環境上,當我們要做對錶做DDL操作的時候,要格外注意,不要因為執行了DDL操作而導致表被鎖住,不能提供正常的DML操作了。

擴充套件閱讀:

  • DML:資料修改語言。對資料庫中表中資料的增刪改查操作,也就是select、insert、update、delete操作。
  • DDL:資料定義語言。對資料庫中表結構的建立或修改操作,也就是create、alter、drop等操作。
  • MDL:後設資料鎖,meta data lock

行鎖

在某個資料行上面加鎖,這個鎖從功能角度上來說可以是共享鎖,也可是排它鎖。

  • 當在某一行上增加了共享鎖之後,其他事務也可以在這個行上面增加一個共享鎖,但是不能在這個行上面增加排它鎖,只能是增加共享鎖。
  • 當在某一行上增加了排它鎖之後,其他事務不可以在這個行上面增加任何鎖。

從功能角度(相容性)劃分

從功能的角度來劃分鎖,可以把鎖分為共享鎖(S鎖/讀鎖)和排它鎖(X鎖/寫鎖)兩種。其中共享鎖和共享鎖可以共存、不互斥;共享鎖和排它鎖不能共存、互斥;排它鎖和排它鎖不能共存、互斥。

它們的關係如下:

互斥關係S鎖x鎖
S鎖可以共存互斥
X鎖互斥互斥

不能共存的意思是:當一個鎖鎖定一個資源後,另外一個鎖如果想同樣使用這個資源,則需要排隊等待前面的鎖釋放後才可以加鎖成功,否則就一直等待,直到事務超時後退出它的事務。

共享鎖

共享鎖,又稱為S鎖,也稱為讀鎖。共享鎖,顧名思義,它是用來共享的。當一個事務對某一個資料增加了共享鎖之後,其他事務仍然可以對這個資料增加共享鎖。但是不可以增加排它鎖。

加共享鎖的方式

select * from t1 where id=3 lock in share mode; -- 給某一行增加共享鎖
select * from t1 lock in share mode; -- 給某一表增加共享鎖

這裡我們來進行實驗驗證一下我們的結論是否正確。

基於級別的共享鎖驗證結果如下:
在這裡插入圖片描述

基於級別的共享鎖驗證結果如下:
在這裡插入圖片描述

排它鎖

排它鎖,又稱為X鎖,也稱為寫鎖。排它鎖,顧名思義,它會排斥其他事務在當前已經增加了排它鎖的資料上增加任何其他的鎖。排它鎖鎖定資料後,這個資料只能讓給它增加排它鎖的事務獨享。

加排它鎖的方式

select * from t1 where id=3 for update; -- 給某一行增加共享鎖
select * from t1 where for update; -- 給某一表增加共享鎖

我們來實驗一下排它鎖的加鎖效果,看下我們的結論是否正確。

基於級別的排它鎖驗證結果如下:
在這裡插入圖片描述

基於級別的排它鎖驗證結果如下:
在這裡插入圖片描述

**注意:**取消共享鎖或排它鎖的方式就是回滾或提交事務。

意向鎖

意向鎖是一種特殊的鎖,他不會真正的去鎖資料。可以把它理解為一種標識。它由MySQL自己維護不需要我們手動的去維護。它屬於表層級的鎖,不會針對某一個行增加意向鎖。 可以分為兩種:意向共享鎖和意向排它鎖,簡稱為IS鎖或IX鎖。

我們舉例說明一下意向鎖的使用場景。

當我們想給某一張表增加S鎖的時候,我們需要保證這張表中的所有資料沒有任何一行資料被增加了X鎖,此時我們才能對這這張表增加S鎖成功。那麼該如何去做這個保證呢?我們需要逐行去驗證每一行是否被增加了X鎖,而如果資料量比較大的時候,我們在做逐行驗證的過程中,不能保證已經驗證通過的資料不會再次被其他事務增加上X鎖。

同時,即便是沒有其他事務對我們驗證通過的行增加X鎖,但如果當我們驗證到最後幾行資料的時候,發現有一個行被增加了X鎖,那麼我們還是不能對這個表增加S鎖的,前面所有的驗證就白白浪費掉了,怎麼樣才能直接告訴我,這個表中的資料是否有某些行被增加了X鎖呢?於是意向鎖就出現了。

當我們向某個中表的某個行增加S鎖或X鎖之前,MySQL會自動給這個表打上一個標記,標記這個表中的資料行已經被增加了S鎖或X鎖。而這個標記就是意向共享鎖IS或意向排它鎖IX。當其他事務嘗試給這個表增加S鎖或X鎖的時候。就直接去看下這個表是否有意向共享鎖IS或者意向排它鎖IX。如果有就直接返回不能對這個表增加對應的鎖。否則就可以直接對錶增加對應的鎖。

  • 事務在獲得表中某行上的共享鎖之前,必須先獲得表上的IS鎖或更強的鎖。
  • 在事務可以獲得表中某一行上的排他鎖之前,它必須首先獲得表上的IX鎖。

IS鎖、IX鎖、S鎖、X鎖的互斥關係如下表所示:

互斥關係S鎖X鎖IS鎖iX鎖
S鎖不互斥互斥不互斥互斥
X鎖互斥互斥互斥互斥
IS鎖不互斥互斥不互斥不互斥
IX鎖互斥互斥不互斥不互斥
意向共享鎖

意向共享鎖(IS)表示事務打算對錶中的各個行設定共享鎖。

當向一個表中的某一行資料增加S鎖之前,MySQL會自動給這個表增加一個意向共享鎖IS。用於標記這個表中的資料已經被增加了S鎖,當其他事務想對這個表增加X鎖的時候,會判斷這個表是否有IS鎖或者IX鎖,如果有,則對錶增加X鎖失敗。否則成功。

意向排它鎖

意向排他鎖(IX)表示事務打算對錶中的各個行設定排他鎖。

當向一個表中的某行資料增加X鎖之前,MySQL會自動給這個表增加一個意向排它鎖IX。用於標記這個表中的資料行已經被增加了X鎖,當前其他事務想對這個表增加X鎖的時候,會判斷這個表是否IS鎖或IX鎖,如果有則直接返回加鎖失敗。否則加鎖成功。

從演算法角度分

記錄鎖

鎖住一行資料,這個可以理解為就是我們平時所說的行鎖。例如下面的例子就是給一行資料增加一個記錄鎖。

select * from t1 where id = 1 for update;

此時id=1的行,不允許任何其他事務進行修改和查詢。

間隙鎖

間隙鎖gap lock,鎖住一個範圍,不讓這個被鎖住的範圍有任何的資料被插入進去,它主要是為了修復可重複讀隔離級別下幻讀的問題。間隙鎖只能鎖住插入的動作,但是此時這個鎖範圍內的資料是可以有更新和刪除操作的。

為了說明間隙鎖,我們來做一個實驗。先準備實驗用的表和資料。他們的SQL如下:

-- 表結構如下
DROP TABLE IF EXISTS `t`;
CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `normal_index_col` int(11) DEFAULT NULL,
  `normal_col` int(11) DEFAULT NULL,
  `unique_index_col` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`), -- 主鍵
  KEY `normal_index_col` (`normal_index_col`), -- 普通索引
  UNIQUE KEY `normal_col` (`normal_col`) -- 唯一索引
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 初始化資料如下
INSERT INTO `t`(`id`, `normal_index_col`, `normal_col`, `unique_index_col`) VALUES (0, 0, 0, 0);
INSERT INTO `t`(`id`, `normal_index_col`, `normal_col`, `unique_index_col`) VALUES (5, 5, 5, 5);
INSERT INTO `t`(`id`, `normal_index_col`, `normal_col`, `unique_index_col`) VALUES (10, 10, 10, 10);
INSERT INTO `t`(`id`, `normal_index_col`, `normal_col`, `unique_index_col`) VALUES (15, 15, 15, 15);
INSERT INTO `t`(`id`, `normal_index_col`, `normal_col`, `unique_index_col`) VALUES (20, 20, 20, 20);
INSERT INTO `t`(`id`, `normal_index_col`, `normal_col`, `unique_index_col`) VALUES (25, 25, 25, 25);

當我們把主鍵ID為0,5,10,15,20,25的6行資料插入之後,我們就把主鍵ID這值拆分為了7個間隙,如下所示:
在這裡插入圖片描述

圖中的7個間隙,就是我們所說的間隙鎖鎖定的地方,每一個間隙就是一個間隙鎖,這些間隙鎖將鎖定空白位置主鍵資料的插入操作,從而避免幻讀的發生。

臨鍵鎖

next-key lock臨鍵鎖=記錄鎖+間隙鎖,是間隙鎖的基礎上,增加一個記錄的值,組成一個左開右閉的區間。它也是用來鎖住一個間隙放置在這個間隙中插入資料而產生幻讀。它的鎖示意圖如下所示:
在這裡插入圖片描述


微信搜尋“coder-home”或掃一掃下面的二維碼,關注公眾號,第一時間瞭解更多幹貨分享,還有各類視訊教程資源。掃描它,帶走我
MySQL中鎖的分類


相關文章