InnoDB的鎖
InnoDB的行鎖:共享鎖、排他鎖、MDL鎖
共享鎖:又稱讀鎖、S鎖。一個(gè)事務(wù)獲取一個(gè)數(shù)據(jù)行的共享鎖,其他事務(wù)能獲取該行對(duì)應(yīng)的共享鎖,但不能獲得排他鎖;即一個(gè)事務(wù)在讀取一個(gè)數(shù)據(jù)行時(shí),其他事務(wù)也可以讀,但不能對(duì)數(shù)據(jù)進(jìn)行增刪改查。
應(yīng)用:
1.自動(dòng)提交模式下的select查詢,不加任何鎖,直接返回查詢結(jié)果
2.通過select……lock in share mode在被讀取的行記錄或范圍上加一個(gè)讀鎖,其他事務(wù)可以讀,但是申請(qǐng)加寫鎖會(huì)被阻塞
排他鎖:又稱寫鎖、X鎖。一個(gè)事務(wù)獲取了一個(gè)數(shù)據(jù)行的寫鎖,其他事務(wù)就不能再獲取該行的其他鎖,寫鎖優(yōu)先級(jí)最高。
應(yīng)用:
1.一些DML操作會(huì)對(duì)行記錄加寫鎖
2.select for update會(huì)對(duì)讀取的行記錄上加一個(gè)寫鎖,其他任何事務(wù)都不能對(duì)鎖定的行加任何鎖,否則會(huì)被阻塞
MDL鎖:MySQL5.5引入,用于保證表中元數(shù)據(jù)的信息。在會(huì)話A中,表開啟了查詢事務(wù)后,會(huì)自動(dòng)獲得一個(gè)MDL鎖,會(huì)話B就不能執(zhí)行任何DDL語句的操作
行鎖實(shí)現(xiàn)方式
InnoDB 行鎖是通過給索引上的索引項(xiàng)加鎖來實(shí)現(xiàn)的,這一點(diǎn) MySQL 與 Oracle 不同,后者是
通過在數(shù)據(jù)塊中對(duì)相應(yīng)數(shù)據(jù)行加鎖來實(shí)現(xiàn)的。InnoDB 這種行鎖實(shí)現(xiàn)特點(diǎn)意味著:只有通過 索引條件檢索數(shù)據(jù),InnoDB
才使用行級(jí)鎖,否則,InnoDB 將使用表鎖! 在實(shí)際應(yīng)用中,要特別注意 InnoDB 行鎖的這一特性,不然的話,可能導(dǎo)致大量的鎖沖突,
從而影響并發(fā)性能。
行鎖的三種算法
InnoDB 存儲(chǔ)引擎有三種行鎖的算法,其分別是:
- Record Lock: 單個(gè)行記錄上的鎖
- Gap Lock: 間隙鎖,鎖定一個(gè)范圍,但不包含記錄本身
- Next-Key 鎖: Gap Lock + Record Lock,鎖定一個(gè)范圍,并且會(huì)鎖定記錄本身
RC模式下只采用Record Lock,RR模式下采用了Next-Key
加鎖場(chǎng)景分析
如果我們加鎖的行上存在主鍵索引,那么就會(huì)在這個(gè)主鍵索引上添加一個(gè) Record Lock。
如果我們加鎖的行上存在輔助索引,那么我們就會(huì)在這行的輔助索引上添加 Next-Key Lock,并在這行之后的輔助索引上添加一個(gè) Gap Lock
輔助索引上的 Next-Key Lock 和 Gap Lock 都是針對(duì) Repeatable Read 隔離模式存在的,這兩種鎖都是為了防止幻讀現(xiàn)象的發(fā)生。
這里有一個(gè)特殊情況,如果輔助索引是唯一索引的話,MySQL 會(huì)將 Next-Key Lock 降級(jí)為 Record Lock,只會(huì)鎖定當(dāng)前記錄的輔助索引。
如果唯一索引由多個(gè)列組成的,而我們只鎖定其中一個(gè)列的話,那么此時(shí)并不會(huì)進(jìn)行鎖降級(jí),還會(huì)添加 Next-Key Lock 和 Gap Lock。
在 InnoDB 存儲(chǔ)引擎中,對(duì)于 Insert 的操作,其會(huì)檢查插入記錄的下一條記錄是否被鎖定,若已經(jīng)被鎖定,則不允許查詢。
意向鎖
意向鎖可以分為意向共享鎖(Intention Shared Lock, IS)和意向排他鎖(Intention eXclusive
Lock,
IX)。但它的鎖定方式和共享鎖和排他鎖并不相同,意向鎖上鎖只是表示一種“意向”,并不會(huì)真的將對(duì)象鎖住,讓其他事物無法修改或訪問。例如事物T1想要修改表test
中的行r1
,它會(huì)上兩個(gè)鎖:
- 在表
test
上意向排他鎖 - 在行
r1
上排他鎖
事物T1在test
表上上了意向排他鎖,并不代表其他事物無法訪問test
了,它上的鎖只是表明一種意向,它將會(huì)在db
中的test
表中的某幾行記錄上上一個(gè)排他鎖。
|
意向共享鎖 |
意向排他鎖 |
共享鎖 |
排他鎖 |
意向共享鎖 |
兼容 |
兼容 |
兼容 |
不兼容 |
意向排他鎖 |
兼容 |
兼容 |
不兼容 |
不兼容 |
共享鎖 |
兼容 |
不兼容 |
兼容 |
不兼容 |
排他鎖 |
不兼容 |
不兼容 |
不兼容 |
不兼容 |
一致性非鎖定讀
一致性非鎖定讀是指 InnoDB 存儲(chǔ)引擎通過行多版本控制(multi
version)的方式來讀取當(dāng)前執(zhí)行時(shí)間數(shù)據(jù)庫中行的數(shù)據(jù)。具體來說就是如果一個(gè)事務(wù)讀取的行正在被鎖定,那么它就會(huì)去讀取這行數(shù)據(jù)之前的快照數(shù)據(jù),而不會(huì)等待這行數(shù)據(jù)上的鎖釋放。這個(gè)讀取流程如圖1所示:

行的快照數(shù)據(jù)是通過undo段來實(shí)現(xiàn)的,而undo段用來回滾事務(wù),所以快照數(shù)據(jù)本身沒有額外的開銷。此外,讀取快照數(shù)據(jù)時(shí)不需要上鎖的,因?yàn)闆]有事務(wù)會(huì)對(duì)快照數(shù)據(jù)進(jìn)行更改。
MySQL 中并不是每種隔離級(jí)別都采用非一致性非鎖定讀的讀取模式,而且就算是采用了一致性非鎖定讀,不同隔離級(jí)別的表現(xiàn)也不相同。在 READ
COMMITTED 和 REPEATABLE READ 這兩種隔離級(jí)別下,InnoDB存儲(chǔ)引擎都使用一致性非鎖定讀。但是對(duì)于快照數(shù)據(jù),READ
COMMITTED 隔離模式中的事務(wù)讀取的是當(dāng)前行最新的快照數(shù)據(jù),而 REPEATABLE READ
隔離模式中的事務(wù)讀取的是事務(wù)開始時(shí)的行數(shù)據(jù)版本。
一致性鎖定讀
在 InnoDB 存儲(chǔ)引擎中,select
語句默認(rèn)采取的是一致性非鎖定讀的情況,但是有時(shí)候我們也有需求需要對(duì)某一行記錄進(jìn)行鎖定再來讀取,這就是一致性鎖定讀。
InnoDB 對(duì)于select
語句支持以下兩種鎖定讀:
select ... for update
select ... lock in share mode
select ... for update
會(huì)對(duì)讀取的記錄加一個(gè)X鎖,其他事務(wù)不能夠再來為這些記錄加鎖。select ... lock in share mode
會(huì)對(duì)讀取的記錄加一個(gè)S鎖,其它事務(wù)能夠再為這些記錄加一個(gè)S鎖,但不能加X鎖。
對(duì)于一致性非鎖定讀,即使行記錄上加了X鎖,它也是能夠讀取的,因?yàn)樗x取的是行記錄的快照數(shù)據(jù),并沒有讀取行記錄本身。
select ... for update
和select ... lock in share mode
這兩個(gè)語句必須在一個(gè)事務(wù)中,當(dāng)事務(wù)提交了,鎖也就釋放了。因此在使用這兩條語句之前必須先執(zhí)行begin
, start transaction
,或者執(zhí)行set autocommit = 0
。
InnoDB 在不同隔離級(jí)別下的一致性讀及鎖的差異
consisten read //一致性讀
share locks //共享鎖
Exclusive locks //排他鎖
|
|
讀未提交 |
讀已提交 |
可重復(fù)讀 |
串行化 |
SQL |
條件 |
|
|
|
|
select |
相等 |
None locks |
Consisten read/None lock |
Consisten read/None lock |
Share locks |
|
范圍 |
None locks |
Consisten read/None lock |
Consisten read/None lock |
Share Next-Key |
update |
相等 |
Exclusive locks |
Exclusive locks |
Exclusive locks |
Exclusive locks |
|
范圍 |
Exclusive next-key |
Exclusive next-key |
Exclusive next-key |
Exclusive next-key |
Insert |
N/A |
Exclusive locks |
Exclusive locks |
Exclusive locks |
Exclusive locks |
Replace |
無鍵沖突 |
Exclusive locks |
Exclusive locks |
Exclusive locks |
Exclusive locks |
|
鍵沖突 |
Exclusive next-key |
Exclusive next-key |
Exclusive next-key |
Exclusive next-key |
delete |
相等 |
Exclusive locks |
Exclusive locks |
Exclusive locks |
Exclusive locks |
|
范圍 |
Exclusive next-key |
Exclusive next-key |
Exclusive next-key |
Exclusive next-key |
Select … from … Lock in share mode |
相等 |
Share locks |
Share locks |
Share locks |
Share locks |
|
范圍 |
Share locks |
Share locks |
Exclusive next-key |
Exclusive next-key |
Select * from … For update |
相等 |
Exclusive locks |
Exclusive locks |
Exclusive locks |
Exclusive locks |
|
范圍 |
Exclusive locks |
Exclusive locks |
Exclusive next-key |
Exclusive next-key |
Insert into … Select … |
innodb_locks_ unsafe_for_bi nlog=off |
Share Next-Key |
Share Next-Key |
Share Next-Key |
Share Next-Key |
(指源表鎖) |
innodb_locks_ unsafe_for_bi nlog=on |
None locks |
Consisten read/None lock |
Consisten read/None lock |
Share Next-Key |
create table … Select … |
innodb_locks_ unsafe_for_bi nlog=off |
Share Next-Key |
Share Next-Key |
Share Next-Key |
Share Next-Key |
(指源表鎖) |
innodb_locks_ unsafe_for_bi nlog=on |
None locks |
Consisten read/None lock |
Consisten read/None lock |
Share Next-Key |
在了解 InnoDB 鎖特性后,用戶可以通過設(shè)計(jì)和 SQL 調(diào)整等措施減少鎖沖突和死鎖,包括:
- 盡量使用較低的隔離級(jí)別;
- 精心設(shè)計(jì)索引,并盡量使用索引訪問數(shù)據(jù),使加鎖更精確,從而減少鎖沖突的機(jī)會(huì);
- 選擇合理的事務(wù)大小, 小事務(wù)發(fā)生鎖沖突的幾率也更小;
- 給記錄集顯示加鎖時(shí),最好一次性請(qǐng)求足夠級(jí)別的鎖。比如要修改數(shù)據(jù)的話,最好直接申請(qǐng)排他鎖,而不是先申請(qǐng)共享鎖,修改時(shí)再請(qǐng)求排他鎖,這樣容易產(chǎn)生死鎖;
- 不同的程序訪問一組表時(shí),應(yīng)盡量約定以相同的順序訪問各表,對(duì)一個(gè)表而言,盡可能以固定的順序存取表中的行。這樣可以大大減少死鎖的機(jī)會(huì);
- 盡量用相等條件訪問數(shù)據(jù),這樣可以避免間隙鎖對(duì)并發(fā)插入的影響;
- 不要申請(qǐng)超過實(shí)際需要的鎖級(jí)別;除非必須,查詢時(shí)不要顯示加鎖;
- 對(duì)于一些特定的事務(wù),可以使用表鎖來提高處理速度或減少死鎖的可能。
參考資料
1.https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-intention-locks mysql官網(wǎng)開發(fā)手冊(cè)
2.《MySQL 技術(shù)內(nèi)幕 – InnoDB 存儲(chǔ)引擎》
3.《深入淺出MySQL》
4.https://www.modb.pro/db/33873