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

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

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

最近,某同事在生產上遇到一個 MySQL 死鎖的問題,于是在幫忙解決問題后,特意花了一周的時間,把 MySQL 所有的鎖都整理了一遍,今天就來一起聊聊 MySQL鎖。

 

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

 

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

 

  • 按照鎖的使用方式,MySQL 鎖可以分成共享鎖、排它鎖兩種;
  • 根據加鎖的范圍,MySQL 鎖大致可以分成全局鎖、表級鎖和行鎖三類;
  • 從思想層面上看,MySQL 鎖可以分為悲觀鎖、樂觀鎖兩種;

 

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

為什么要掌握 MySQL 鎖

作為經常和 MySQL 數據庫打交道的程序員來說,編寫業務 SQL 語句是在所難免,編寫高性能的 SQL 語句更是一種技術能力的體現。鎖作為 MySQL 數據庫一個非常重要的知識點,不但是面試中的高頻問題,更是通往技術高P崗位的必備技能。

共享鎖&排他鎖 1.共享鎖

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

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

如果事務T1 在某對象持有共享(S)鎖,則事務T2 需要再次獲取該對象的鎖時,會出現下面兩種情況: - 如果T2 獲取該對象的共享(S)鎖,則可以立即獲取鎖; - 如果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;


 

img.png

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

加鎖線程 sessionA

線程B sessionB

線程C sessionC

#開啟事務
begin;

#給user表id=3的行加共享鎖
select * from user
where 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操作執行ok
delete from user where id = 3;


 

img.png

通過上述兩個實例可以看出: - 當共享鎖加在user表上,則其它事務可以再次獲取user表的共享鎖,其它事務再次獲取user表的排他鎖失敗,操作被堵塞; - 當共享鎖加在user表id=3的行上,則其它事務可以再次獲取user表id=3行上的共享鎖,其它事務再次獲取user表id=3行上的排他鎖失敗,操作被堵塞,但是事務可以再次獲取user表id!=3行上的排他鎖;

2. 排它鎖

排它鎖,Exclusive Lock,也叫寫鎖或者獨占鎖,主要是防止其它事務和當前加鎖事務鎖定同一對象。同一對象主要有兩層含義: - 當排他鎖加在表上,則其它事務無法對該表進行insert,update,delete,alter,drop等更新操作; - 當排他鎖加在表的行上,則其它事務無法對該行進行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;


 

img.png

給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;


 

img.png

全局鎖&表級鎖&行鎖 1. 全局鎖 1.1 定義

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

1.2 加鎖

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

flush tables with read lock

指令執行完,整個數據庫就處于只讀狀態了,其他線程執行以下操作,都會被阻塞: - 數據更新語句被阻塞,包括 insert, update, delete語句; - 數據定義語句被阻塞,包括建表 create table,alter table、drop table 語句; - 更新操作事務commit語句被阻塞;

1.3 釋放鎖

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


 

img.png

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

1.4 使用場景

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


 

img.png

 

  • 假如在主庫上備份,備份期間,業務服務器不能對數據庫執行更新操作,因此涉及到更新操作的業務就癱瘓了;
  • 假如在從庫上備份,備份期間,從庫不能執行主庫同步過來的 binlog,會導致主從延遲越來越大,如果做了讀寫分離,那么從庫上獲取數據就會出現延時,影響業務;

 

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


 

img.png

 

  • 備份前:賬戶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. 表級鎖

MySQL 表級鎖有兩種: 表鎖 和 元數據鎖(metadata lock,MDL)

2.1 表鎖

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

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

讀鎖:代表當前表為只讀狀態,讀鎖是一種共享鎖。需要注意的是,讀鎖除了會限制其它線程的操作外,也會限制加鎖線程的行為,具體限制如下: 1. 加鎖線程只能對當前表進行讀操作,不能對當前表進行更新操作,不能對其它表進行所有操作; 2. 其它線程只能對當前表進行讀操作,不能對當前表進行更新操作,可以對其它表進行所有操作;

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

加鎖線程 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


 

img.png

寫鎖:寫鎖是一種獨占鎖,需要注意的是,寫鎖除了會限制其它線程的操作外,也會限制加鎖線程的行為,具體限制如下: 1. 加鎖線程對當前表能進行所有操作,不能對其它表進行任何操作; 2. 其它線程不能對當前表進行任何操作,可以對其它表進行任何操作;

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

加鎖線程 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


 

img.png

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


 

img.png

為了更好的說明 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


 

img.png

2.3 意向鎖

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

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

意向鎖通常有兩種類型: - 意向共享鎖(IS),表示事務打算在表中的各個行上設置共享鎖。 - 意向排他鎖(IX),表示事務打算對表中的各個行設置排他鎖。

意向鎖是InnoDB自動加上的,加鎖時遵從下面兩個協議: - 事務在獲取表中行的共享鎖之前,必須先獲取表上的IS鎖或更強的鎖。 - 事務在獲取表中行的排他鎖之前,必須先獲取表上的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


 

img.png

加鎖線程 sessionA

線程B sessionB

#開啟事務
begin;

#user表id=6加排他鎖,默認user表會 加上IX鎖
select * from user where id = 6 for update;

# 觀察IX鎖
select * from performance_schema.data_locksG


 

img.png

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鎖,可以參照下面的順序執行流和實例圖:

2.5 鎖的兼容性

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

X

IX

S

IS

X

沖突

沖突

沖突

沖突

IX

沖突

兼容

沖突

兼容

S

沖突

沖突

兼容

兼容

IS

沖突

兼容

兼容

兼容

3. 行鎖

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

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

 

  1. Record Lock: 記錄鎖,是在索引記錄上加鎖;
  2. Gap Lock:間隙鎖,鎖定一個范圍,但不包含記錄;
  3. Next-key Lock:Gap Lock + Record Lock,鎖定一個范圍(Gap Lock實現),并且鎖定記錄本身(Record Lock實現);
  4. 插入意向鎖:針對 insert 操作產生的意向鎖;
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


 

img.png

3.2 Gap Lock

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


 

img.png

為了更好的說明 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


 

img.png

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

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

3.3 Next-Key Lock

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


 

img.png

為了更好的說明 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


 

img.png

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

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);


 

img.png

樂觀鎖&悲觀鎖

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

 

  • 悲觀鎖,可以理解成:在對任意記錄進行修改前,先嘗試為該記錄加上排他鎖(exclusive locking),采用的是先獲取鎖再操作數據的策略,可能會產生死鎖;
  • 樂觀鎖,是相對悲觀鎖而言,一般不會利用數據庫的鎖機制,而是采用類似版本號比較之類的操作,因此樂觀鎖不會產生死鎖的問題;
死鎖和死鎖檢測

 

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

show engine innodb statusG

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

 

  • 一種策略是,直接進入等待,直到超時。這個超時時間可以通過參數 innodb_lock_wait_timeout 來設置,InnoDB 中 innodb_lock_wait_timeout 的默認值是 50s。
  • 另一種策略是,發起死鎖檢測,發現死鎖后,主動回滾死鎖鏈條中的某一個事務,讓其它事務得以繼續執行。將參數 innodb_deadlock_detect 設置為 on,表示開啟死鎖檢測。

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

網友整理

注冊時間:

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

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

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

數獨大挑戰2018-06-03

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

答題星2018-06-03

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

全階人生考試2018-06-03

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

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

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

每日養生app2018-06-03

每日養生,天天健康

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

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