1. 程式人生 > 其它 >14--mysql鎖機制

14--mysql鎖機制

目錄

一、 資料庫的鎖機制

什麼是鎖?為何要加入鎖機制?

鎖是計算機協調多個程序或執行緒併發訪問某一資源的機制(保障資料的機制),那為何要加入鎖機制呢?

因為在資料庫中,除了傳統的計算資源(如CPU、RAM、I/O等)的爭用以外,資料也是一種供需要使用者共享的資源。

當併發事務同時訪問一個共享的資源時,有可能導致資料不一致、資料無效等問題,

例如我們在資料庫的讀現象中介紹過,在併發訪問情況下,可能會出現髒讀、不可重複讀和幻讀等讀現象

為了應對這些問題,主流資料庫都提供了鎖機制,以及事務隔離級別的概念,

而鎖機制可以將併發的資料訪問順序化,以保證資料庫中資料的一致性與有效性

此外,鎖衝突也是影響資料庫併發訪問效能的一個重要因素,鎖對資料庫而言顯得尤其重要,也更加複雜。

為何要用鎖

以互斥鎖為例,讓多個併發的任務同一時間只有一個執行(注意這不是序列),犧牲了效率但換來資料安全

封鎖、時間戳、樂觀併發控制(樂觀鎖)和悲觀併發控制(悲觀鎖)是併發控制主要採用的技術手段。

總鎖的優缺點:

​ 優點--保障併發場景下的資料安全

​ 缺點---降低了效率

所以我們在使用鎖時應儘可能縮小鎖的範圍,即鎖住的資料越少越好,併發能力越高

二、鎖的分類

1.*按鎖的粒度劃分,可分為行級鎖表級鎖頁級鎖。(mysql支援)

2.*按鎖級別劃分,可分為共享鎖排他鎖

3.*按使用方式劃分,可分為樂觀鎖悲觀鎖

4、按加鎖方式劃分,可分為自動鎖、顯式

5、按操作劃分,可分為DML鎖DDL鎖

DML鎖(data locks,資料鎖),用於保護資料的完整性,其中包括行級鎖(Row Locks (TX鎖))、表級鎖(table lock(TM鎖))。

DDL鎖(dictionary locks,資料字典鎖),用於保護資料庫物件的結構,如表、索引等的結構定義。其中包排他DDL鎖(Exclusive DDL lock)、共享DDL鎖(Share DDL lock)、可中斷解析鎖(Breakable parse locks)

三、行級鎖,表級鎖,頁級鎖(粒度)

在DBMS中,可以按照鎖的粒度把資料庫鎖分為行級鎖(INNODB引擎)、表級鎖(MYISAM引擎)和頁級鎖(BDB引擎 )。

1.行級鎖

行級鎖是Mysql中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。行級鎖能大大減少資料庫操作的衝突。其加鎖粒度最小,但加鎖的開銷也最大。行級鎖分為共享鎖排他鎖

  • 特點:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高
  • 支援引擎:InnoDB
  • 行級鎖定分為行共享讀鎖(共享鎖)與行獨佔寫鎖(排他鎖)
共享鎖(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他鎖(X):SELECT * FROM table_name WHERE ... FOR UPDATE

2.表級鎖(偏向於讀)

表級鎖是MySQL中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少,被大部分MySQL引擎支援。最常使用的MYISAM與INNODB都支援表級鎖定。表級鎖定分為表共享讀鎖(共享鎖)與表獨佔寫鎖(排他鎖)。

  • 特點:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發出鎖衝突的概率最高,併發度最低
  • 支援引擎:MyISAM、MEMORY、InNoDB
  • 分類:表級鎖定分為表共享讀鎖(共享鎖)與表獨佔寫鎖(排他鎖),如下所示
lock table 表名 read(write),表名 read(write),.....;
# 給表加讀鎖或者寫鎖,例如
mysql> lock table employee write;  #鎖寫只准自己讀寫,別人不可讀,寫。如果是鎖住讀行為,所有人只能讀
Query OK, 0 rows affected (0.00 sec)

mysql> show open tables where in_use>= 1;
+----------+----------+--------+-------------+
| Database | Table    | In_use | Name_locked |
+----------+----------+--------+-------------+
| ttt      | employee |      1 |           0 |
+----------+----------+--------+-------------+
1 row in set (0.00 sec)

mysql> unlock tables;                #  釋放被當前會話持有的任何鎖
Query OK, 0 rows affected (0.00 sec)

mysql> show open tables where in_use>= 1;
Empty set (0.00 sec)

3.頁級鎖

頁級鎖是MySQL中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但衝突多,行級衝突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。BDB支援頁級鎖

特點:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。

四 、行級鎖之--共享鎖與排他鎖(級別)

行級鎖分為共享鎖和排他鎖兩種。

與行處理相關的sql有:insert、update、delete、select,這四類sql在操作記錄行時,可以為行加上鎖,但需要知道的是:

對於insert、update、delete語句,InnoDB會自動給涉及的資料加鎖,而且是排他鎖(X);

對於普通的select語句,InnoDB不會加任何鎖,需要我們手動自己加,可以加兩種型別的鎖,如下所示

# 共享鎖(S):SELECT ... LOCK IN SHARE MODE;  -- 查出的記錄行都會被鎖住

# 排他鎖(X):SELECT ... FOR UPDATE;  -- 查出的記錄行都會被鎖住

1. 共享鎖(Share Lock)

共享鎖又稱為讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對於同一資料可以共享一把鎖,獲准共享鎖的事務只能讀資料,不能修改資料直到已釋放所有共享鎖,所以共享鎖可以支援併發讀

如果事務T對資料A加上共享鎖後,則其他事務只能對A再加共享鎖或不加鎖(在其他事務裡一定不能再加排他鎖,但是在事務T自己裡面是可以加的),反之亦然。

用法

SELECT ... LOCK IN SHARE MODE;

在查詢語句後面增加LOCK IN SHARE MODE,Mysql會對查詢結果中的每行都加共享鎖,當沒有其他執行緒對查詢結果集中的任何一行使用排他鎖時,可以成功申請共享鎖,否則會被阻塞。其他執行緒也可以讀取使用了共享鎖的表,而且這些執行緒讀取的是同一個版本的資料。

2. 排他鎖(eXclusive Lock)

排他鎖又稱為寫鎖,互斥鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其他所並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再對該行加任何型別的其他他鎖(共享鎖和排他鎖),但是獲取排他鎖的事務是可以對資料就行讀取和修改。

寫操作預設加排它鎖

用法

SELECT ... FOR UPDATE;

在查詢語句後面增加FOR UPDATE,Mysql會對查詢結果中的每行都加排他鎖,當沒有其他執行緒對查詢結果集中的任何一行使用排他鎖時,可以成功申請排他鎖,否則會被阻塞。

特例:加過排他鎖的資料行在其他事務種是不能修改資料的,也不能通過for update和lock in share mode鎖的方式查詢資料,但可以直接通過select ...from...查詢資料,因為普通select查詢沒有任何鎖機制。

3. 意向鎖

意向鎖是表級鎖,其設計目的主要是為了在一個事務中揭示下一行將要被請求鎖的型別。

意向鎖的作用就是當一個事務在需要獲取資源鎖定的時候,如果遇到自己需要的資源已經被排他鎖佔用的時候,該事務可以需要鎖定行的表上面新增一個合適的意向鎖。
如果自己需要一個共享鎖,那麼就在表上面新增一個意向共享鎖。而如果自己需要的是某行(或者某些行)上面新增一個排他鎖的話,則先在表上面新增一個意向排他鎖

InnoDB中有兩個意向鎖(表鎖):

(1)意向共享鎖(IS):事務打算給資料行共享鎖;,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖
(2)意向排他鎖(IX)事務打算給資料行加排他鎖;事務在給一個數據行加排他鎖前必須先取得該表的IX鎖

意向鎖是InnoDB自動加的,不需要使用者干預。

五、 Innodb儲存引擎的鎖機制

mysql常用儲存引擎的鎖機制

MyISAM和MEMORY採用表級鎖(table-level locking)

BDB採用頁面鎖(page-level locking)或表級鎖,預設為頁面鎖

InnoDB支援行級鎖(row-level locking)和表級鎖,預設為行級鎖(偏向於寫)

1 .行級鎖與表級鎖的使用區分

MyISAM 操作資料都是使用表級鎖,MyISAM總是一次性獲得所需的全部鎖,要麼全部滿足,要麼全部等待。所以不會產生死鎖,但是由於每操作一條記錄就要鎖定整個表,導致效能較低,併發不高。

InnoDB 與 MyISAM 的最大不同有兩點:一是 InnoDB 支援事務;二是 InnoDB 採用了行級鎖。也就是你需要修改哪行,就可以只鎖定哪行。

在Mysql中,行級鎖並不是直接鎖記錄,而是鎖索引。InnoDB 行鎖是通過給索引項加鎖實現的,而索引分為主鍵索引和非主鍵索引兩種

1)、如果一條sql 語句操作了主鍵索引,Mysql 就會鎖定這條語句命中的主鍵索引(或稱聚簇索引);

2)、如果一條語句操作了非主鍵索引(或稱輔助索引),MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。

3)、如果沒有索引,InnoDB 會通過隱藏的聚簇索引來對記錄加鎖。也就是說:如果不通過索引條件檢索資料,那麼InnoDB將對錶中所有資料加鎖,實際效果跟表級鎖一樣。

在實際應用中,要特別注意InnoDB行鎖的這一特性,不然的話,可能導致大量的鎖衝突,從而影響併發效能。

  • 1、在不通過索引條件查詢的時候,InnoDB 的效果就相當於表鎖
  • 2、當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行,另外,不論 是使用主鍵索引、唯一索引或普通索引,InnoDB 都會使用行鎖來對資料加鎖。
  • 3、由於 MySQL 的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以即便你的sql語句訪問的是不同的記錄行,但如果命中的是相同的被鎖住的索引鍵,也還是會出現鎖衝突的。
  • 4、即便在條件中使用了索引欄位,但是否使用索引來檢索資料是由 MySQL 通過判斷不同 執行計劃的代價來決定的,如果 MySQL 認為全表掃 效率更高,比如對一些很小的表,它 就不會使用索引,這種情況下 InnoDB 將鎖住所有行,相當於表鎖。因此,在分析鎖衝突時, 別忘了檢查 SQL 的執行計劃,以確認是否真正使用了索引

2 .三種行鎖的演算法

InnoDB有三種行鎖的演算法,都屬於排他鎖:

  • 1、Record Lock:命中索引才鎖行。
  • 2、Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄本身。GAP鎖的目的,是為了防止同一事務的兩次當前讀,出現幻讀的情況。
當我們用範圍條件而不是相等條件檢索資料,並請求共享或排他鎖時,InnoDB會給符合條件的已有資料記錄的索引項加鎖;
對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖(Next-Key鎖)。 

# 例如
例:假如employee表中只有101條記錄,其depid的值分別是 1,2,...,100,101,下面的SQL:
mysql> select * from emp where depid > 100 for update;是一個範圍條件的檢索,並且命中了索引,InnoDB不僅會對符合條件的empid值為101的記錄加鎖,也會對empid大於101(這些記錄並不存在)的“間隙”加鎖。
  • 3、Next-Key Lock:等於Record Lock結合Gap Lock,也就說Next-Key Lock既鎖定記錄本身也鎖定一個範圍特別需要注意的是,InnoDB儲存引擎還會對輔助索引下一個鍵值加上gap lock

對於行查詢,如果命中的索引不是唯一索引,innodb採用的都是Next-Key Lock,主要目的是解決幻讀的問題,以滿足相關隔離級別以及恢復和複製的需要

因為InnoDB對於行的查詢都是採用了Next-Key Lock的演算法,鎖定的不是單個值,而是一個範圍。但是,當查詢的索引含有唯一屬性的時候,Next-Key Lock 會進行優化,將其降級為Record Lock,即僅鎖住索引本身,不是範圍。

注意:通過主鍵或則唯一索引來鎖定不存在的值,也會產生GAP鎖定。

3 .死鎖問題

MyISAM中是不會產生死鎖的,因為MyISAM總是一次性獲得所需的全部鎖,要麼全部滿足,要麼全部等待。而在InnoDB中,鎖是逐步獲得的,就造成了死鎖的可能。

總結:
1)、如果沒有命中索引,無論你篩選出哪一行,都會將整張表鎖住
2)、如果命中了非唯一索引,並且是等值查詢,會鎖行還有間隙
3)、如果命中了非唯一索引,但是是範圍查詢,會鎖行還有間隙

4)、如果命中了唯一索引,並且是等值查詢,只會鎖定行
5)、如果命中了唯一索引,並且是範圍查詢,會鎖行還有間隙

 下面,來看看兩個死鎖的例子 (一個是兩個Session的兩條SQL產生死鎖;另一個是兩個Session的一條SQL,產生死鎖):

create table t1(id int primary key,name varchar(20))engine =innodb;
		
insert into t1 values
(1,"egon1"),
(3,"egon2"),
(5,"egon3"),
(8,"egon4"),
(11,"egon5");
		
死鎖現象1:

# 事務1	
begin; -- 步驟1
select * from t1 where id=1 for update;  -- 步驟2

update t1 set name="EGON" where id=5;  -- 步驟5

# 事務2:
begin;  -- 步驟3

select * from t1 where id=5 for update;  -- 步驟4

delete from t1 where id=1;  -- 步驟6

死鎖現象2:高併發場景下,核心原理是:命中了輔助索引,會先鎖定輔助索引,再鎖定聚集索引

 上面的兩個死鎖用例。第一個非常好理解,也是最常見的死鎖,每個事務執行兩條SQL,分別持有了一把鎖,然後加另一把鎖,產生死鎖

 第二個用例,只有多個事務同時執行的情況下才可能出現,但隱蔽性極強,雖然每個Session都只有一條語句,仍舊會產生死鎖。要分析這個死鎖,首先必須用到本文前面提到的MySQL加鎖的規則。針對Session 1,從name索引出發,讀到的[hdc, 1],[hdc, 6]均滿足條件,不僅會加name索引上的記錄X鎖,而且會加聚簇索引上的記錄X鎖,加鎖順序為先[1,hdc,100],後[6,hdc,10]。而Session 2,從pubtime索引出發,[10,6],[100,1]均滿足過濾條件,同樣也會加聚簇索引上的記錄X鎖,加鎖順序為[6,hdc,10],後[1,hdc,100]。發現沒有,跟Session 1的加鎖順序正好相反,如果兩個Session恰好都持有了第一把鎖,請求加第二把鎖,死鎖就發生了。

結論:

# 1、關於死鎖問題需要儲備的知識
在MySQL中,行級鎖並不是直接鎖記錄,而是鎖索引。索引分為主鍵索引和非主鍵索引兩種,如果一條sql語句操作了主鍵索引,MySQL就會鎖定這條主鍵索引;如果一條語句操作了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。 在UPDATE、DELETE操作時,MySQL不僅鎖定WHERE條件掃描過的所有索引記錄,而且會鎖定相鄰的鍵值,即所謂的next-key locking。

# 2、死鎖產生的本質原理
死鎖的發生與否,並不在於事務中有多少條SQL語句,死鎖的關鍵在於:兩個(或以上)的Session加鎖的順序不一致。而使用本文上面提到的,分析MySQL每條SQL語句的加鎖規則,分析出每條語句的加鎖順序,然後檢查多個併發SQL間是否存在以相反的順序加鎖的情況,就可以分析出各種潛在的死鎖情況,也可以分析出線上死鎖發生的原因。

發生死鎖後,InnoDB一般都可以檢測到,並使一個事務釋放鎖回退,另一個獲取鎖完成事務。

有多種方法可以避免死鎖,

(1)如果不同程式會併發存取多個表,儘量約定以相同的順序訪問表,可以大大降低死鎖機會。
(2)在同一個事務中,儘可能做到一次鎖定所需要的所有資源,減少死鎖產生概率;
(3)對於非常容易產生死鎖的業務部分,可以嘗試使用升級鎖定顆粒度,通過表級鎖定來減少死鎖產生的概率;
(4)在程式以批量方式處理資料的時候,如果事先對資料排序,保證每個執行緒按固定的順序來處理記錄,也可以大大降低出現死鎖的

4.什麼時候使用表鎖

絕大部分情況使用行鎖,但在個別特殊事務中,也可以考慮使用表鎖

1、事務需要更新大部分資料,表又較大
若使用預設的行鎖,不僅該事務執行效率低(因為需要對較多行加鎖,加鎖是需要耗時的); 而且可能造成其他事務長時間鎖等待和鎖衝突; 這種情況下可以考慮使用表鎖來提高該事務的執行速度
2、事務涉及多個表,較複雜,很可能引起死鎖,造成大量事務回滾
這種情況也可以考慮一次性鎖定事務涉及的表,從而避免死鎖、減少資料庫因事務回滾帶來的開銷當然,應用中這兩種事務不能太多,否則,就應該考慮使用MyISAM

5 .行鎖優化建議

通過檢查InnoDB_row_lock狀態變數來分析系統上的行鎖的爭奪情況,在著手根據狀態量來分析改善;

show status like 'innodb_row_lock%';//檢視行鎖的狀態
  • 儘可能讓所有資料檢索都通過索引來完成, 從而避免無索引行鎖升級為表鎖
  • 合理設計索引,儘量縮小鎖的範圍
  • 儘可能減少檢索條件,避免間隙鎖
  • 儘量控制事務大小,減少鎖定資源量和時間長度
  • 儘可能低級別事務隔離,

六、樂觀鎖與悲觀鎖(使用方式)

1、悲觀鎖

悲觀鎖,正如其名,它指的是對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度(悲觀),因此,在整個資料處理過程中,將資料處於鎖定狀態。 悲觀鎖的實現,往往依靠資料庫提供的鎖機制 (也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料),現在網際網路高併發的架構中,受到fail-fast思路的影響,悲觀鎖已經非常少見了。

在資料庫中,悲觀鎖的流程如下:

在對任意記錄進行修改前,先嚐試為該記錄加上排他鎖(exclusive locking)。

如果加鎖失敗,說明該記錄正在被修改,那麼當前查詢可能要等待或者丟擲異常。 具體響應方式由開發者根據實際需要決定。

如果成功加鎖,那麼就可以對記錄做修改,事務完成後就會解鎖了。

其間如果有其他對該記錄做修改或加排他鎖的操作,都會等待我們解鎖或直接丟擲異常。

ps:行鎖、表鎖、讀鎖、寫鎖都是在操作之前先上排他鎖

在資料表中的實現

在MySQL中使用悲觀鎖,必須關閉MySQL的自動提交,set autocommit=0,因為MySQL預設使用自動提交autocommit模式,在執行完sql後會自動提交併釋放鎖

set autocommit=0;

總結

悲觀併發控制主要用於資料爭用激烈的環境,以及發生併發衝突時使用鎖保護資料的成本要低於回滾事務的成本的環境中。

優點:

悲觀併發控制實際上是“先取鎖再訪問”的保守策略,為資料處理的安全提供了保證。
缺點:
(a)在效率方面,處理加鎖的機制會讓資料庫產生額外的開銷,還有增加產生死鎖的機會;
(b) 在只讀型事務處理中由於不會產生衝突,也沒必要使用鎖,這樣做只能增加系統負載;還有會降低了並行性,一個事務如果鎖定了某行資料,其他事務就必須等待該事務處理完才可以處理那行數

2、樂觀鎖(大多數用的鎖)

樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖假設認為資料一般情況下不會造成衝突,所以在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果發現衝突了,則讓返回使用者錯誤的資訊,讓使用者決定如何去做。

優點與不足

樂觀併發控制相信事務之間的資料競爭(data race)的概率是比較小的,因此儘可能直接做下去,直到提交的時候才去鎖定,所以不會產生任何鎖和死鎖。

如何選擇

在樂觀鎖與悲觀鎖的選擇上面,主要看下兩者的區別以及適用場景就可以了。

1、樂觀鎖並未真正加鎖,效率高。一旦鎖的粒度掌握不好,更新失敗的概率就會比較高,容易發生業務失敗。

2、悲觀鎖依賴資料庫鎖,效率低。更新失敗的概率比較低。

隨著網際網路三高架構(高併發、高效能、高可用)的提出,悲觀鎖已經越來越少的被使用到生產環境中了,尤其是併發量比較大的業務場景。