日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:52000
  • 待審:37
  • 小程序:12
  • 文章:1037587
  • 會員:756

基于讀者反饋 肝了一周,這下徹底把 MySQL的鎖搞懂了 排版有些不太清晰,特整理了一個新排版:

申明:本文基于 MySQL 8.0.30 版本,InnoDB 引擎

MySQL 數據庫鎖設計的初衷是處理并發問題,保證數據安全。MySQL 數據庫鎖可以從下面 3 個維度進行劃分:

  1. 按照鎖的使用方式,MySQL 鎖可以分成共享鎖、排他鎖兩種;

  2. 根據加鎖的范圍,MySQL 鎖大致可以分成全局鎖、表級鎖和行鎖三類;

  3. 從思想層面上看,MySQL 鎖可以分為悲觀鎖、樂觀鎖兩種;

     

我們先講解共享鎖和排它鎖,然后講解全局鎖、表級鎖和行鎖,因為這三種類別的鎖中,有些是共享鎖,有些是排他鎖,最后再講解悲觀鎖和樂觀鎖。

圖片

 

一、共享鎖和排他鎖

1.1 共享鎖

共享鎖,Share lock,也叫讀鎖。它是指當對象被鎖定時,允許其它事務讀取該對象,也允許其它事務從該對象上再次獲取共享鎖,但不能對該對象進行寫入。加鎖方式有如下兩種方式:

#方式1select ... lock in share mode;#方式2select ... for share;

 

如果事務 T1 在某對象持有共享(S)鎖,則事務 T2 需要再次獲取該對象的鎖時,會出現下面兩種情況:

  1. 如果 T2 獲取該對象的共享(S)鎖,則可以立即獲取鎖;

  2. 如果 T2 獲取該對象的排他(X)鎖,則無法獲取鎖;

     

為了更好的理解上述兩種情況,可以參照下面的執行順序流和實例圖:

給 user 表加共享鎖

加鎖線程 sessionA 線程 B sessionB
#開啟事務
begin;
 
#對 user 整張表加共享鎖
select * from user lock in share mode;
 
  #獲取 user 表上的共享鎖 ok,select 操作成功執行
select * from user;
  #獲取 user 表上的排他鎖失敗,操作被堵塞
delete from user where id = 1;
#提交事務
#user 表上的共享鎖被釋放
commit;
 
  #獲取 user 表上的排他鎖成功,delete 操作執行 ok
delete from user where id = 1;

圖片

 

給 user 表 id=3 的行加共享鎖

加鎖線程 sessionA 線程 B sessionB 線程 C sessionC
#開啟事務begin;    
#給 user 表 id=3 的行加共享鎖select * from userwhere id = 3 lock in share mode;    
  #獲取 user 表 id=3 行上的共享鎖 ok#select 操作執行成功select * from user where id=3; #獲取 user 表 id=3 行上的共享鎖 ok#select 操作執行成功select * from user where id=3;
  #獲取 user 表 id=3 行上的排它鎖失敗#delete 操作被堵塞delete from user where id = 3; #獲取 user 表 id=4 行上的排它鎖成功#delete 操作執行成功delete from user where id = 4;
#提交事務#user 表 id=3 的行上共享鎖被釋放commit;    
  #獲取 user 表 id=3 行上的排它鎖成功#被堵塞的 delete 操作執行 okdelete from user where id = 3;  

圖片

通過上述兩個實例可以看出:

  • 當共享鎖加在 user 表上,則其它事務可以再次獲取 user 表的共享鎖,其它事務再次獲取 user 表的排他鎖失敗,操作被堵塞;

  • 當共享鎖加在 user 表 id=3 的行上,則其它事務可以再次獲取 user 表 id=3 行上的共享鎖,其它事務再次獲取 user 表 id=3 行上的排他鎖失敗,操作被堵塞,但是事務可以再次獲取 user 表 id!=3 行上的排他鎖;

 

1.2 排他鎖

排它鎖,Exclusive Lock,也叫寫鎖或者獨占鎖,主要是防止其它事務和當前加鎖事務鎖定同一對象。同一對象主要有兩層含義:

  1. 當排他鎖加在表上,則其它事務無法對該表進行 insert,update,delete,alter,drop 等更新操作;

  2. 當排他鎖加在表的行上,則其它事務無法對該行進行 insert,update,delete,alter,drop 等更新操作;

     

排它鎖加鎖方式為:

??????# 排它鎖加鎖方式select ... for update;

 

為了更好地說明排他鎖,可以參照下面的執行順序流和實例圖:

給 user 表對象加排他鎖

加鎖線程 sessionA 線程 B sessionB
#開啟事務 begin;  
#對 user 整張表加排他鎖
select * from user for update;
 
  #獲取 user 表上的共享鎖 ok,select 執行成功
select * from user;
  #獲取 user 表上的排他鎖失敗,操作被堵塞
delete from user where id=3;
#提交事務
#user 表上的排他被釋放
commit;
 
  #獲取 user 表上的排他鎖成功,操作執行 ok
delete from user where id = 1;

圖片

給 user 表 id=3 的行對象加排他鎖

加鎖線程 sessionA 線程 B sessionB 線程 C sessionC
#開啟事務
begin;
   
#給 user 表 id=3 的行加排他鎖
select * from user
where id = 3 for update;
   
  #獲取 user 表 id=3 行上的共享鎖 ok
select * from user where id=3;
#獲取 user 表 id=3 行上的共享鎖 ok
select * from user where id=3;
  #獲取user表id=3 行上的排它鎖失敗
delete from user where id = 3;
#獲取 user 表 id=4 行上的排它鎖成功
delete from user where id = 4;
#提交事務
#user 表 id=3 的行上排他鎖被釋放
commit;
   
  #獲取 user 表 id=3 行上的排它鎖成功
#被堵塞的 delete 操作執行 ok
delete from user where id = 3;
 

圖片

 

二、全局鎖 & 表鎖 & 行鎖

2.1 全局鎖

全局鎖,顧名思義,就是對整個數據庫實例加鎖,它是粒度最大的鎖。

在 MySQL 中,通過執行 flush tables with read lock 指令加全局鎖:

??????# 加全局鎖flush tables with read lock

 

指令執行完,整個數據庫就處于只讀狀態了,其他線程執行以下操作,都會被阻塞:

  • 數據更新語句被阻塞,包括 insert, update, delete 語句;

  • 數據定義語句被阻塞,包括建表 create table,alter table、drop table 語句;

  • 更新操作事務 commit 語句被阻塞;

 

MySQl 釋放鎖有 2 種方式:

  1. 執行 unlock tables 指令:unlock tables

  2. 加鎖的會話斷開,全局鎖也會被自動釋放

 

為了更好地說明全局鎖,可以參照下面的執行順序流和實例圖:

加鎖線程 sessionA 線程 B sessionB
flush tables with read lock; 加全局鎖  
select user 表 ok select user 表 ok
insert user 表堵塞 insert user 表堵塞
delete user 表堵塞 delete user 表堵塞
drop user 表堵塞 drop user 表堵塞
alter user 表 堵塞 alter user 表 堵塞
unlock tables;解鎖  
被堵塞的修改操作執行 ok 被堵塞的修改操作執行 ok

圖片

通過上述的實例可以看出,當加全局鎖時,庫下面所有的表都處于只讀狀態,不管是當前事務還是其他事務,對于庫下面所有的表只能讀,不能執行 insert,update,delete,alter,drop 等更新操作。

全局鎖的典型使用場景是做全庫邏輯備份,在備份過程中整個庫完全處于只讀狀態。如下圖:

圖片

假如在主庫上備份,備份期間,業務服務器不能對數據庫執行更新操作,因此涉及到更新操作的業務就癱瘓了;

假如在從庫上備份,備份期間,從庫不能執行主庫同步過來的 binlog,會導致主從延遲越來越大,如果做了讀寫分離,那么從庫上獲取數據就會出現延時,影響業務;

從上述分析可以看出,使用全局鎖進行數據備份,不管是在主庫還是在從庫上進行備份操作,對業務總是不太友好。那不加鎖行不行?我們可以通過下面還錢轉賬的例子,看看不加鎖會不會出現問題:

圖片

  • 備份前:賬戶 A 有 1000,賬戶 B 有 500

  • 此時,發起邏輯備份

  • 假如數據備份時不加鎖,此時,客戶端 A 發起一個還錢轉賬的操作:賬戶 A 往賬戶 B 轉 200

  • 當賬戶 A 轉出 200 完成,賬戶 B 轉入 200 還未完成時,整個數據備份完成

  • 如果用該備份數據做恢復,會發現賬戶 A 轉出了 200,賬戶 B 卻沒有對應的轉入記錄,這樣就會產生糾紛:A 說我賬戶少了 200, B 說我沒有收到,最后,A,B 誰都不干。

既然不加鎖會產生錯誤,加全局鎖又會影響業務,那么有沒有兩全其美的方式呢?

有,MySQL 官方自帶的邏輯備份工具 mysqldump,具體指令如下:

??????# 備份數據mysqldump –single-transaction

執行該指令,在備份數據之前會先啟動一個事務,來確保拿到一致性視圖, 加上 MVCC 的支持,保證備份過程中數據是可以正常更新。但是,single-transaction 方法只適用于庫中所有表都使用了事務引擎,如果有表使用了不支持事務的引擎,備份就只能用 FTWRL 方法。

2.2 表級鎖   

MySQL 表級鎖有兩種:

  1. 表鎖

  2. 元數據鎖(metadata lock,MDL)

 

2.2.1 表鎖

表鎖就是對整張表加鎖,包含讀鎖和寫鎖,由 MySQL Server 實現,表鎖需要顯示加鎖或釋放鎖,具體指令如下:

#給表加寫鎖lock tables tablename write;
# 給表加讀鎖lock tables tablename read;
# 釋放鎖unlock tables;

 

讀鎖代表當前表為只讀狀態,讀鎖是一種共享鎖。需要注意的是,讀鎖除了會限制其它線程的操作外,也會限制加鎖線程的行為,具體限制如下:

  • 加鎖線程只能對當前表進行讀操作,不能對當前表進行更新操作,不能對其它表進行所有操作;

  • 其它線程只能對當前表進行讀操作,不能對當前表進行更新操作,可以對其它表進行所有操作;

 

為了更好地說明讀鎖,可以參照下面的執行順序流和實例圖:

加鎖線程 sessionA 線程 B sessionB
#給 user 表加讀鎖
lock tables user read;
 
select user 表 ok select user 表 ok
insert user 表被拒絕 insert user 表堵塞
insert address 表被拒絕 insert address 表 ok
select address 表被拒絕 alter user 表堵塞
unlock tables; 釋放鎖  
  被堵塞的修改操作執行 ok

圖片

寫鎖:寫鎖是一種獨占鎖,需要注意的是,寫鎖除了會限制其它線程的操作外,也會限制加鎖線程的行為,具體限制如下:

  • 加鎖線程對當前表能進行所有操作,不能對其它表進行任何操作;

  • 其它線程不能對當前表進行任何操作,可以對其它表進行任何操作;

     

為了更好的說明寫鎖,可以參照下面的執行順序流和實例圖:

加鎖線程 sessionA 線程 B sessionB
#給 user 表加寫鎖
lock tables user write;
 
select user 表 ok select user 表 ok
insert user 表被拒絕 insert user 表堵塞
insert address 表被拒絕 insert address 表 ok
select address 表被拒絕 alter user 表堵塞
unlock tables; 釋放鎖  
  堵塞在 user 表的上更新操作執行 ok

圖片

 

2.2.2 MDL 元數據鎖   

元數據鎖:metadata lock,簡稱 MDL,它是在 MySQL 5.5 版本引進的。元數據鎖不用像表鎖那樣顯式的加鎖和釋放鎖,而是在訪問表時被自動加上,以保證讀寫的正確性。加鎖和釋放鎖規則如下:

  • MDL 讀鎖之間不互斥,也就是說,允許多個線程同時對加了 MDL 讀鎖的表進行 CRUD(增刪改查)操作;

  • MDL 寫鎖,它和讀鎖、寫鎖都是互斥的,目的是用來保證變更表結構操作的安全性。也就是說,當對表結構進行變更時,會被默認加 MDL 寫鎖,因此,如果有兩個線程要同時給一個表加字段,其中一個要等另一個執行完才能開始執行。

  • MDL 讀寫鎖是在事務 commit 之后才會被釋放;

     

為了更好的說明 MDL 讀鎖規則,可以參照下面的順序執行流和實例圖:

加鎖線程 sessionA 其它線程 sessionB
開啟事務
begin;
 
select user 表,user 表會默認加上 MDL 讀鎖  
select user 表 ok select user 表 ok
insert user 表 ok insert user 表 ok
update user 表 ok update user 表 ok
delete user 表 ok delete user 表 ok
  alter user 表,獲取 MDL 寫鎖失敗,操作被堵塞
commit;提交事務,MDL 讀鎖被釋放  
  被堵塞的修改操作執行 ok

圖片

為了更好的說明 MDL 寫鎖規則,可以參照下面的順序執行流和實例圖:

加鎖線程 sessionA 線程 B sessionB 線程 C sessionC
#開啟事務
begin;
   
#user 表會默認加上 MDL 讀鎖
select user 表,
   
select user 表 ok select user 表 ok select user 表 ok
  #獲取 MDL 寫鎖失敗
alter user 表操作被堵塞
#獲取 MDL 讀鎖失敗
select * from user;
提交事務,MDL 讀鎖被釋放    
  #MDL 寫鎖被釋放
被堵塞的 alter user 操作執行 ok
 
    #被堵塞的 select 操作執行 ok

圖片

 

2.2.3 意向鎖

由于 InnoDB 引擎支持多粒度鎖定,允許行鎖和表鎖共存,為了快速的判斷表中是否存在行鎖,InnoDB 推出了意向鎖。

意向鎖,Intention lock,它是一種表鎖,用來標識事務打算在表中的行上獲取什么類型的鎖。不同的事務可以在同一張表上獲取不同種類的意向鎖,但是第一個獲取表上意向排他(IX) 鎖的事務會阻止其它事務獲取該表上的任何 S 鎖 或 X 鎖。反之,第一個獲得表上意向共享鎖(IS) 的事務可防止其它事務獲取該表上的任何 X 鎖。

意向鎖通常有兩種類型:

  1. 意向共享鎖(IS),表示事務打算在表中的各個行上設置共享鎖。

  2. 意向排他鎖(IX),表示事務打算對表中的各個行設置排他鎖。


意向鎖是 InnoDB 自動加上的,加鎖時遵從下面兩個協議:

  1. 事務在獲取表中行的共享鎖之前,必須先獲取表上的 IS 鎖或更強的鎖。

  2. 事務在獲取表中行的排他鎖之前,必須先獲取表上的 IX 鎖。

     

為了更好地說明意向共享鎖,可以參照下面的順序執行流和實例圖:

加鎖線程 sessionA 線程 B sessionB
#開啟事務
begin;
 
#user 表 id=6 加共享行鎖 ,默認 user 表會 加上 IS 鎖
select * from user where id = 6 for share;
 
  # 觀察 IS 鎖
select* from performance_schema.data_locksG

圖片

加鎖線程 sessionA 線程 B sessionB
#開啟事務
begin;
 
#user 表 id=6 加排他鎖,默認 user 表會 加上 IX 鎖
select * from user where id = 6 for update;
 
  # 觀察 IX 鎖
select* from performance_schema.data_locksG

圖片

 

2.2.4 AUTO-INC 鎖

AUTO-INC 鎖是一種特殊的表級鎖,當表中有 AUTO_INCREMENT 的列時,如果向這張表插入數據時,InnoDB 會先獲取這張表的 AUTO-INC 鎖,等插入語句執行完成后,AUTO-INC 鎖會被釋放。

AUTO-INC 鎖可以使用 innodb_autoinc_lock_mode 變量來配置自增鎖的算法,innodb_autoinc_lock_mode 變量可以選擇三種值如下表:

innodb_autoinc_lock_mode 含義
0 傳統鎖模式,采用 AUTO-INC 鎖
1 連續鎖模式,采用輕量級鎖
2 交錯鎖模式(MySQL8 默認),AUTO-INC 和輕量級鎖之間靈活切換

為了更好的說明 AUTO-INC 鎖,可以參照下面的順序執行流和實例圖:

 

表級鎖的兼容性 

下面的圖表總結了表級鎖類型的兼容性

  X IX S IS
X 沖突 沖突 沖突 沖突
IX 沖突 兼容 沖突 兼容
S 沖突 沖突 兼容 兼容
IS 沖突 兼容 兼容 兼容

 

2.3 行鎖   

行鎖是針對數據表中行記錄的鎖。MySQL 的行鎖是在引擎層實現的,并不是所有的引擎都支持行鎖,比如,InnoDB 引擎支持行鎖而 MyISAM 引擎不支持。

InnoDB 引擎的行鎖主要有四類:

  1. Record Lock:記錄鎖,是在索引記錄上加鎖;

  2. Gap Lock:間隙鎖,鎖定一個范圍,但不包含記錄;

  3. Next-key Lock:Gap Lock + Record Lock,鎖定一個范圍(Gap Lock 實現),并且鎖定記錄本身(Record Lock 實現);

  4. 插入意向鎖;

 

2.3.1 Record Lock

Record Lock:記錄鎖,是針對索引記錄的鎖,鎖定的總是索引記錄。

例如,select id from user where id = 1 for update; for update 就顯式在索引 id 上加行鎖(排他鎖),防止其它任何事務 update 或 delete id=1 的行,但是對 user 表的 insert、alter、drop 操作還是可以正常執行。

為了更好的說明 Record Lock 鎖,可以參照下面的執行順序流和實例圖:

加鎖線程 sessionA 線程 B sessionB 線程 B sessionC
#開啟事務
begin;
   
給 user 表 id=1 加寫鎖
select id from user
where id = 1 for update;
   
  update user set name = 'name121'
where id = 1;
 
    #查看 InnoDB 監視器中記錄鎖數據
show engine innodb statusG
commit 提交事務
record lock 被釋放
   
  被堵塞的 update 操作執行 ok  

圖片

 

2.3.2 Gap Lock 

Gap Lock:間隙鎖,鎖住兩個索引記錄之間的間隙上,由 InnoDB 隱式添加。比如(1,3) 表示鎖住記錄 1 和記錄 3 之間的間隙,這樣記錄 2 就無法插入,間隙可能跨越單個索引值、多個索引值,甚至是空。

圖片

為了更好的說明 Gap Lock 間隙鎖,可以參照下面的順序執行流和實例圖:

加鎖線程 sessionA 線程 B sessionB 線程 C sessionC
#開啟事務
begin;
   
加鎖
select * from user
where age = 10 for share;
   
  insert into user(id,age) values(2,20);  
    #查看 InnoDB 監視器中記錄鎖數據
show engine innodb statusG
commit 提交事務
Gap Lock 被釋放
   
  被堵塞的 insert 操作執行 ok  

圖片

上圖中,事務 A(sessionA)在加共享鎖的時候產生了間隙鎖(Gap Lock),事務 B(sessionB)對間隙中進行 insert/update 操作,需要先獲取排他鎖(X),導致阻塞。事務 C(sessionC)通過"show engine innodb statusG" 指令可以查看到間隙鎖的存在。需要說明的,間隙鎖只是鎖住間隙內部的范圍,在間隙外的 insert/update 操作不會受影響。

Gap Lock 鎖,只存在于可重復讀隔離級別,目的是為了解決可重復讀隔離級別下幻讀的現象。

 

2.3.3 Next-Key Lock 

Next-Key 鎖,稱為臨鍵鎖,它是 Record Lock + Gap Lock 的組合,用來鎖定一個范圍,并且鎖定記錄本身鎖,它是一種左開右閉的范圍,可以用符號表示為:(a,b]。

圖片

為了更好的說明 Next-Key Lock 間隙鎖,可以參照下面的順序執行流和實例圖:

加鎖線程 sessionA 線程 B sessionB 線程 C sessionC 線程 D sessionD
#開啟事務
begin;
     
加鎖
select * from user
where age = 10 for share;
     
  #獲取鎖失敗,insert 操作被堵塞
insert into user(id,age)
values(2,20);
   
    update user set name='name1'
where age = 10;
#查看 InnoDB 監視器中記錄鎖數據
show engine innodb statusG
提交事務 Gap Lock 被釋放
commit
     
  被堵塞的 insert 操作執行 ok 被堵塞的 update 操作執行 ok  

圖片

上圖中,事務 A(sessionA)在加共享鎖的時候產生了間隙鎖(Gap Lock),事務 B(sessionB)對間隙中進行 insert 操作,需要先獲取排他鎖(X),導致阻塞。事務 C(sessionC)對間隙中進行 update 操作,需要先獲取排他鎖(X),導致阻塞。事務 D(sessionD)通過"show engine innodb statusG" 指令可以查看到間隙鎖的存在。需要說明的,間隙鎖只是鎖住間隙內部的范圍,在間隙外的 insert/update 操作不會受影響。

 

2.3.4 Insert Intention Lock   

插入意向鎖,它是一種特殊的間隙鎖,特指插入操作產生的間隙鎖。為了更好的說明 Insert Intention Lock 鎖,可以參照下面的順序執行流和實例圖:

加鎖線程 sessionA 線程 B sessionB 線程 C sessionC
#開啟事務
begin;
   
加鎖
select * from user
where age = 10 for share;
   
  #獲取鎖失敗,insert 操作被堵塞
insert into user(id,age) values(2,20);
 
    #查看 InnoDB 監視器中記錄鎖數據
show engine innodb statusG
commit 提交事務
Gap Lock 被釋放
   
  #被堵塞的 insert 操作執行 ok
insert into user(id,age) values(2,20);
 

圖片

 

三、樂觀鎖 & 悲觀鎖   

在 MySQL 中,無論是悲觀鎖還是樂觀鎖,都是人們對概念的一種思想抽象,它們本身還是利用 MySQL 提供的鎖機制來實現的。其實,除了在 MySQL 數據,像 JAVA 語言里面也有樂觀鎖和悲觀鎖的概念。

  • 悲觀鎖,可以理解成:在對任意記錄進行修改前,先嘗試為該記錄加上排他鎖(exclusive locking),采用的是先獲取鎖再操作數據的策略,可能會產生死鎖;

  • 樂觀鎖,是相對悲觀鎖而言,一般不會利用數據庫的鎖機制,而是采用類似版本號比較之類的操作,因此樂觀鎖不會產生死鎖的問題;

 

四、死鎖 & 死鎖檢測   

當并發系統中不同線程出現循環資源依賴,涉及的線程都在等待別的線程釋放資源時,就會導致這幾個線程都進入無限等待的狀態,稱為死鎖。可以通過下面的指令查看死鎖:

  • show engine innodb statusG

當出現死鎖以后,有兩種策略:

  1. 直接進入等待,直到超時。這個超時時間可以通過參數 innodb_lock_wAIt_timeout 來設置,InnoDB 中 innodb_lock_wait_timeout 的默認值是 50s。

  2. 發起死鎖檢測,發現死鎖后,主動回滾死鎖鏈條中的某一個事務,讓其它事務得以繼續執行。將參數 innodb_deadlock_detect 設置為 on,表示開啟死鎖檢測。

 

五、總結   

本文基于 MySQL 8.0.30 版本和 InnoDB 引擎,對 MySQL 中的鎖進行了講解,每種鎖都有其特定的使用場景。作為經常和 MySQL 打交道的 Java 程序員來說,對 MySQL 鎖了解的越深,越可以幫助我們更好的去寫出高性能的 SQL 語句。

分享到:
標簽:MySQL
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 52000

    網站

  • 12

    小程序

  • 1037587

    文章

  • 756

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定