大廠面試官必問(wèn)的Mysql鎖機(jī)制
前言
前幾天有粉絲和我聊到他找工作面試大廠時(shí)被問(wèn)的問(wèn)題,因?yàn)楝F(xiàn)在疫情期間,找工作也特別難找。他說(shuō)面試的題目也比較難,都偏向于一兩年的工作經(jīng)驗(yàn)的面試題。
他說(shuō)在一面的時(shí)候被問(wèn)到Mysql的面試題,索引那塊自己都回答比較滿意,但是問(wèn)到Mysql的鎖機(jī)制就比較懵了。
因?yàn)槠綍r(shí)沒(méi)有關(guān)注Mysql的鎖機(jī)制,當(dāng)被問(wèn)到高并發(fā)場(chǎng)景下鎖機(jī)制是怎么保證數(shù)據(jù)的一致性的和事務(wù)隔離性的。
他把他面試的過(guò)程分享給了我,Mysql高并發(fā)鎖機(jī)制的問(wèn)題,幾乎面大廠都有被問(wèn)到,Mysql怎么在高并發(fā)下控制并發(fā)訪問(wèn)的?
我細(xì)想了一下,Mysql的鎖機(jī)制確實(shí)非常重要,所以在這里做一個(gè)全面的總結(jié)整理,便于以后的查閱,也分享給各位讀者大大們。
Mysql的鎖機(jī)制還是有點(diǎn)難理解的,所以這篇文章采用圖文結(jié)合的方式講解難點(diǎn),幫助大家理解,講解的主要內(nèi)容如下圖的腦圖所示,基本涵蓋了Mysql鎖機(jī)制的所有知識(shí)點(diǎn)。
本文腦圖
鎖種類(lèi)
Mysql中鎖的分類(lèi)按照不同類(lèi)型的劃分可以分成不同的鎖,按照「鎖的粒度」劃分可以分成:「表鎖、頁(yè)鎖、行鎖」;按照「使用的方式」劃分可以分為:「共享鎖」和「排它鎖」;按照思想的劃分:「樂(lè)觀鎖」和「悲觀鎖」。
下面我們對(duì)著這幾種劃分的鎖進(jìn)行詳細(xì)的解說(shuō)和介紹,在了解設(shè)計(jì)者設(shè)計(jì)鎖的概念的同時(shí),也能深入的理解設(shè)計(jì)者的設(shè)計(jì)思想。
「表鎖」是粒度最大的鎖,開(kāi)銷(xiāo)小,加鎖快,不會(huì)出現(xiàn)死鎖,但是由于粒度太大,因此造成鎖的沖突幾率大,并發(fā)性能低。
Mysql的「MyISAM儲(chǔ)存引擎就支持表鎖」,MyISAM的表鎖模式有兩種:「表共享讀鎖」和「表獨(dú)占寫(xiě)鎖」。
當(dāng)一個(gè)線程獲取到MyISAM表的讀鎖的時(shí)候,會(huì)阻塞其他用戶(hù)對(duì)該表的寫(xiě)操作,但是不會(huì)阻塞其它用戶(hù)對(duì)該用戶(hù)的讀操作。
相反的,當(dāng)一個(gè)線程獲取到MyISAM表的寫(xiě)鎖的時(shí)候,就會(huì)阻塞其它用戶(hù)的讀寫(xiě)操作對(duì)其它的線程具有排它性。
「頁(yè)鎖」的粒度是介于行鎖和表鎖之間的一種鎖,因?yàn)轫?yè)鎖是在BDB中支持的一種鎖機(jī)制,也很少?zèng)]人提及和使用,所以這里制作概述,不做詳解。
「行鎖」是粒度最小的鎖機(jī)制,行鎖的加鎖開(kāi)銷(xiāo)性能大,加鎖慢,并且會(huì)出現(xiàn)死鎖,但是行鎖的鎖沖突的幾率低,并發(fā)性能高。
行鎖是InnoDB默認(rèn)的支持的鎖機(jī)制,MyISAM不支持行鎖,這個(gè)也是InnoDB和MyISAM的區(qū)別之一。
行鎖在使用的方式上可以劃分為:「共享讀鎖(S鎖)「和」排它寫(xiě)鎖(X鎖)」。
當(dāng)一個(gè)事務(wù)對(duì)Mysql中的一條數(shù)據(jù)行加上了S鎖,當(dāng)前事務(wù)不能修改該行數(shù)據(jù)只能執(zhí)行讀操作,其他事務(wù)只能對(duì)該行數(shù)據(jù)加S鎖不能加X(jué)鎖。
若是一個(gè)事務(wù)對(duì)一行數(shù)據(jù)加了X鎖,該事務(wù)能夠?qū)υ撔袛?shù)據(jù)執(zhí)行讀和寫(xiě)操作,其它事務(wù)不能對(duì)該行數(shù)據(jù)加任何的鎖,既不能讀也不能寫(xiě)。
「悲觀鎖和樂(lè)觀鎖是在很多框架都存在的一種思想,不要狹義地認(rèn)為它們是某一種框架的鎖機(jī)制」。
數(shù)據(jù)庫(kù)管理系統(tǒng)中為了控制并發(fā),保證在多個(gè)事務(wù)執(zhí)行時(shí)的數(shù)據(jù)一致性以及事務(wù)的隔離性,使用悲觀鎖和樂(lè)觀鎖來(lái)解決并發(fā)場(chǎng)景下的問(wèn)題。
Mysql的「悲觀鎖的實(shí)現(xiàn)是基于Mysql自身的鎖機(jī)制實(shí)現(xiàn),而樂(lè)觀鎖需要程序員自己去實(shí)現(xiàn)的鎖機(jī)制」,最常見(jiàn)的樂(lè)觀鎖實(shí)現(xiàn)就鎖機(jī)制是「使用版本號(hào)實(shí)現(xiàn)」。
樂(lè)觀鎖設(shè)計(jì)思想的在CAS的運(yùn)用也是比較經(jīng)典,之前我寫(xiě)過(guò)一篇關(guān)于CAS的文章,大家感興趣的可以參考這一篇[深入剖析AQS和CAS,看了都說(shuō)好]。
從上面的介紹中說(shuō)了每一種鎖的概念,但是很難說(shuō)哪一種鎖就是最好的,鎖沒(méi)有最好的,只有哪種業(yè)務(wù)場(chǎng)景最適合哪種鎖,具體業(yè)務(wù)具體分析。
下面我們就具體基于Mysql的存儲(chǔ)引擎詳細(xì)的分析每一種鎖在存儲(chǔ)引擎中的運(yùn)用和實(shí)現(xiàn)。
MyISAM
MyISAM中默認(rèn)支持的表級(jí)鎖有兩種:「共享讀鎖」和「獨(dú)占寫(xiě)鎖」。表級(jí)鎖在MyISAM和InnoDB的存儲(chǔ)引擎中都支持,但是InnoDB默認(rèn)支持的是行鎖。
Mysql中平時(shí)讀寫(xiě)操作都是隱式的進(jìn)行加鎖和解鎖操作,Mysql已經(jīng)自動(dòng)幫我們實(shí)現(xiàn)加鎖和解鎖操作了,若是想要測(cè)試鎖機(jī)制,我們就要顯示的自己控制鎖機(jī)制。
Mysql中可以通過(guò)以下sql來(lái)顯示的在事務(wù)中顯式的進(jìn)行加鎖和解鎖操作:
// 顯式的添加表級(jí)讀鎖
LOCK TABLE 表名 READ
// 顯示的添加表級(jí)寫(xiě)鎖
LOCK TABLE 表名 WRITE
// 顯式的解鎖(當(dāng)一個(gè)事務(wù)commit的時(shí)候也會(huì)自動(dòng)解鎖)
unlock tables;
下面我們就來(lái)測(cè)試一下MyISAM中的表級(jí)鎖機(jī)制,首先創(chuàng)建一個(gè)測(cè)試表employee ,這里要指定存儲(chǔ)引擎為MyISAM,并插入兩條測(cè)試數(shù)據(jù):
CREATE TABLE IF NOT EXISTS employee (
id INT PRIMARY KEY auto_increment,
name VARCHAR(40),
money INT
)ENGINE MyISAM
INSERT INTO employee(name, money) VALUES('黎杜', 1000);
INSERT INTO employee(name, money) VALUES('非科班的科班', 2000);
查看一下,表結(jié)果如下圖所示:
MyISAM表級(jí)寫(xiě)鎖
(1)與此同時(shí)再開(kāi)啟一個(gè)session窗口,然后在第一個(gè)窗口執(zhí)行下面的sql,在session1中給表添加寫(xiě)鎖:
LOCK TABLE employee WRITE
(2)可以在session2中進(jìn)行查詢(xún)或者插入、更新該表數(shù)據(jù),可以發(fā)現(xiàn)都會(huì)處于等待狀態(tài),也就是session1鎖住了整個(gè)表,導(dǎo)致session2只能等待:
(3)在session1中進(jìn)行查詢(xún)、插入、更新數(shù)據(jù),都可以執(zhí)行成功:
「總結(jié):」 從上面的測(cè)試結(jié)果顯示「當(dāng)一個(gè)線程獲取到表級(jí)寫(xiě)鎖后,只能由該線程對(duì)表進(jìn)行讀寫(xiě)操作,別的線程必須等待該線程釋放鎖以后才能操作」。
MyISAM表級(jí)共享讀鎖
(1)接下來(lái)測(cè)試一下表級(jí)共享讀鎖,同樣還是利用上面的測(cè)試數(shù)據(jù),第一步還是在session1給表加讀鎖。
(2)然后在session1中嘗試進(jìn)行插入、更新數(shù)據(jù),發(fā)現(xiàn)都會(huì)報(bào)錯(cuò),只能查詢(xún)數(shù)據(jù)。
(3)最后在session2中嘗試進(jìn)行插入、更新數(shù)據(jù),程序都會(huì)進(jìn)入等待狀態(tài),只能查詢(xún)數(shù)據(jù),直到session1解鎖表session2才能插入、更新數(shù)據(jù)。
「總結(jié):」 從上面的測(cè)試結(jié)果顯示「當(dāng)一個(gè)線程獲取到表級(jí)讀鎖后,該線程只能讀取數(shù)據(jù)不能修改數(shù)據(jù),其它線程也只能加讀鎖,不能加寫(xiě)鎖」。
MyISAM表級(jí)鎖競(jìng)爭(zhēng)情況
MyISAM存儲(chǔ)引擎中,可以通過(guò)查詢(xún)變量來(lái)查看并發(fā)場(chǎng)景鎖的爭(zhēng)奪情況,具體執(zhí)行下面的sql語(yǔ)句:
show status like 'table%';
主要是查看table_locks_waited和table_locks_immediate的值的大小分析鎖的競(jìng)爭(zhēng)情況。
Table_locks_immediate:表示能夠立即獲得表級(jí)鎖的鎖請(qǐng)求次數(shù);Table_locks_waited表示不能立即獲取表級(jí)鎖而需要等待的鎖請(qǐng)求次數(shù)分析,「值越大競(jìng)爭(zhēng)就越嚴(yán)重」。
并發(fā)插入
通過(guò)上面的操作演示,詳細(xì)的說(shuō)明了表級(jí)共享鎖和表級(jí)寫(xiě)鎖的特點(diǎn)。但是在平時(shí)的執(zhí)行sql的時(shí)候,這些「解鎖和釋放鎖都是Mysql底層隱式的執(zhí)行的」。
上面的演示只是為了證明顯式的執(zhí)行事務(wù)的過(guò)程共享鎖和表級(jí)寫(xiě)鎖的加鎖和解鎖的特點(diǎn),實(shí)際并不會(huì)這么做的。
在我們平時(shí)執(zhí)行select語(yǔ)句的時(shí)候就會(huì)隱式的加讀鎖,執(zhí)行增、刪、改的操作時(shí)就會(huì)隱式的執(zhí)行加寫(xiě)鎖。
MyISAM存儲(chǔ)引擎中,雖然讀寫(xiě)操作是串行化的,但是它也支持并發(fā)插入,這個(gè)需要設(shè)置內(nèi)部變量concurrent_insert的值。
它的值有三個(gè)值0、1、2??梢酝ㄟ^(guò)以下的sql查看concurrent_insert的默認(rèn)值為「AUTO(或者1)」。
concurrent_insert的值為NEVER (or 0)表示不支持比并發(fā)插入;值為AUTO(或者1)表示在MyISAM表中沒(méi)有被刪除的行,運(yùn)行另一個(gè)線程從表尾插入數(shù)據(jù);值為ALWAYS (or 2)表示不管是否有刪除的行,都允許在表尾插入數(shù)據(jù)。
鎖調(diào)度
MyISAM存儲(chǔ)引擎中,「假如同時(shí)一個(gè)讀請(qǐng)求,一個(gè)寫(xiě)請(qǐng)求過(guò)來(lái)的話,它會(huì)優(yōu)先處理寫(xiě)請(qǐng)求」,因?yàn)镸yISAM存儲(chǔ)引擎中認(rèn)為寫(xiě)請(qǐng)求比讀請(qǐng)求重要。
這樣就會(huì)導(dǎo)致,「假如大量的讀寫(xiě)請(qǐng)求過(guò)來(lái),就會(huì)導(dǎo)致讀請(qǐng)求長(zhǎng)時(shí)間的等待,或者"線程餓死",因此MyISAM不適合運(yùn)用于大量讀寫(xiě)操作的場(chǎng)景」,這樣會(huì)導(dǎo)致長(zhǎng)時(shí)間讀取不到用戶(hù)數(shù)據(jù),用戶(hù)體驗(yàn)感極差。
當(dāng)然可以通過(guò)設(shè)置low-priority-updates參數(shù),設(shè)置請(qǐng)求鏈接的優(yōu)先級(jí),使得Mysql優(yōu)先處理讀請(qǐng)求。
InnoDB
InnoDB和MyISAM不同的是,InnoDB支持「行鎖」和「事務(wù)」,行級(jí)鎖的概念前面以及說(shuō)了,這里就不再贅述,事務(wù)的四大特性的概述以及實(shí)現(xiàn)的原理可以參考這一篇[]。
InnoDB中除了有「表鎖」和「行級(jí)鎖」的概念,還有Gap Lock(間隙鎖)、Next-key Lock鎖,「間隙鎖主要用于范圍查詢(xún)的時(shí)候,鎖住查詢(xún)的范圍,并且間隙鎖也是解決幻讀的方案」。
InnoDB中的行級(jí)鎖是「對(duì)索引加的鎖,在不通過(guò)索引查詢(xún)數(shù)據(jù)的時(shí)候,InnoDB就會(huì)使用表鎖」。
「但是通過(guò)索引查詢(xún)的時(shí)候是否使用索引,還要看Mysql的執(zhí)行計(jì)劃」,Mysql的優(yōu)化器會(huì)判斷是一條sql執(zhí)行的最佳策略。
若是Mysql覺(jué)得執(zhí)行索引查詢(xún)還不如全表掃描速度快,那么Mysql就會(huì)使用全表掃描來(lái)查詢(xún),這是即使sql語(yǔ)句中使用了索引,最后還是執(zhí)行為全表掃描,加的是表鎖。
若是對(duì)于Mysql的sql執(zhí)行原理不熟悉的可以參考這一篇文章[]。最后是否執(zhí)行了索引查詢(xún)可以通過(guò)explain來(lái)查看,我相信這個(gè)大家都是耳熟能詳?shù)拿盍恕?/p>
InnoDB行鎖和表鎖
InnoDB的行鎖也是分為行級(jí)「共享讀鎖(S鎖)「和」排它寫(xiě)鎖(X鎖)」,原理特點(diǎn)和MyISAM的表級(jí)鎖兩種模式是一樣的。
若想顯式的給表加行級(jí)讀鎖和寫(xiě)鎖,可以執(zhí)行下面的sql語(yǔ)句:
// 給查詢(xún)sql顯示添加讀鎖
select ... lock in share mode;
// 給查詢(xún)sql顯示添加寫(xiě)鎖
select ... for update;
(1)下面我們直接進(jìn)入鎖機(jī)制的測(cè)試階段,還是創(chuàng)建一個(gè)測(cè)試表,并插入兩條數(shù)據(jù):
// 先把原來(lái)的MyISAM表給刪除了
DROP TABLE IF EXISTS employee;
CREATE TABLE IF NOT EXISTS employee (
id INT PRIMARY KEY auto_increment,
name VARCHAR(40),
money INT
)ENGINE INNODB;
// 插入測(cè)試數(shù)據(jù)
INSERT INTO employee(name, money) VALUES('黎杜', 1000);
INSERT INTO employee(name, money) VALUES('非科班的科班', 2000);
(2)創(chuàng)建的表中可以看出對(duì)表中的字段只有id添加了主鍵索引,接著就是在session1窗口執(zhí)行begin開(kāi)啟事務(wù),并執(zhí)行下面的sql語(yǔ)句:
// 使用非索引字段查詢(xún),并顯式的添加寫(xiě)鎖
select * from employee where name='黎杜' for update;
(3)然后在session2中執(zhí)行update語(yǔ)句,上面查詢(xún)的是id=1的數(shù)據(jù)行,下面update的是id=2的數(shù)據(jù)行,會(huì)發(fā)現(xiàn)程序也會(huì)進(jìn)入等待狀態(tài):
update employee set name='ldc' where id =2;
可見(jiàn)若是「使用非索引查詢(xún),直接就是使用的表級(jí)鎖」,鎖住了整個(gè)表。
(4)若是session1使用的是id來(lái)查詢(xún),如下圖所示:
(5)那么session2是可以成功update其它數(shù)據(jù)行的,但是這里我建議使用數(shù)據(jù)量大的表進(jìn)行測(cè)試,因?yàn)榍懊嫖艺f(shuō)過(guò)了「是否執(zhí)行索引還得看Mysql的執(zhí)行計(jì)劃,對(duì)于一些小表的操作,可能就直接使用全表掃描」。
(6)還有一種情況就是:假如我們給name字段也加上了普通索引,那么通過(guò)普通索引來(lái)查詢(xún)數(shù)據(jù),并且查詢(xún)到多行數(shù)據(jù),那它是鎖這多行數(shù)據(jù)還是鎖整個(gè)表呢?
下面我們來(lái)測(cè)試一下,首先給「name字段添加普通索引」,如下圖所示:
(6)并插入一條新的數(shù)據(jù)name值與id=2的值相同,并顯式的加鎖,如下若是:
(7)當(dāng)update其它數(shù)據(jù)行name值不是ldc的也會(huì)進(jìn)入等待狀態(tài),并且通過(guò)explain來(lái)查看是否name='ldc'有執(zhí)行索引,可以看到sql語(yǔ)句是有執(zhí)行索引條件的。


結(jié)論:從上面的測(cè)試鎖機(jī)制的演示可以得出以下幾個(gè)結(jié)論:
-
執(zhí)行非索引條件查詢(xún)執(zhí)行的是表鎖。 -
執(zhí)行索引查詢(xún)是否是加行鎖,還得看Mysql的執(zhí)行計(jì)劃,可以通過(guò)explain關(guān)鍵字來(lái)查看。 -
用普通鍵索引的查詢(xún),遇到索引值相同的,也會(huì)對(duì)其他的操作數(shù)據(jù)行的產(chǎn)生影響。
InnoDB間隙鎖
當(dāng)我們使用范圍條件查詢(xún)而不是等值條件查詢(xún)的時(shí)候,InnoDB就會(huì)給符合條件的范圍索引加鎖,在條件范圍內(nèi)并不存的記錄就叫做"間隙(GAP)"
大家大概都知道在事務(wù)的四大隔離級(jí)別中,不可重復(fù)讀會(huì)產(chǎn)生幻讀的現(xiàn)象,只能通過(guò)提高隔離級(jí)別到串行化來(lái)解決幻讀現(xiàn)象。
但是Mysql中的不可重復(fù)是已經(jīng)解決了幻讀問(wèn)題,它通過(guò)引入間隙鎖的實(shí)現(xiàn)來(lái)解決幻讀,通過(guò)給符合條件的間隙加鎖,防止再次查詢(xún)的時(shí)候出現(xiàn)新數(shù)據(jù)產(chǎn)生幻讀的問(wèn)題。
例如我們執(zhí)行下面的sql語(yǔ)句,就會(huì)對(duì)id大于100的記錄加鎖,在id>100的記錄中肯定是有不存在的間隙:
Select * from employee where id> 100 for update;
(1)接著來(lái)測(cè)試間隙鎖,新增一個(gè)字段num,并將num添加為普通索引、修改之前的數(shù)據(jù)使得num之間的值存在間隙,操作如下sql所示:
alter table employee add num int not null default 0;
update employee set num = 1 where id = 1;
update employee set num = 1 where id = 2;
update employee set num = 3 where id = 3;
insert into employee values(4,'kris',4000,5);
(2)接著在session1的窗口開(kāi)啟事務(wù),并執(zhí)行下面操作:
(3)同時(shí)打開(kāi)窗口session2,并執(zhí)行新增語(yǔ)句:
insert into employee values(5,'ceshi',5000,2); // 程序出現(xiàn)等待
insert into employee values(5,'ceshi',5000,4); // 程序出現(xiàn)等待
insert into employee values(5,'ceshi',5000,6); // 新增成功
insert into employee values(6,'ceshi',5000,0); // 新增成功
「從上面的測(cè)試結(jié)果顯示在區(qū)間(1,3]U[3,5)之間加了鎖,是不能夠新增數(shù)據(jù)行,這就是新增num=2和num=4失敗的原因,但是在這個(gè)區(qū)間以外的數(shù)據(jù)行是沒(méi)有加鎖的,可以新增數(shù)據(jù)行」。
根據(jù)索引的有序性,而普通索引是可以出現(xiàn)重復(fù)值,那么當(dāng)我們第一個(gè)sesson查詢(xún)的時(shí)候只出現(xiàn)一條數(shù)據(jù)num=3,為了解決第二次查詢(xún)的時(shí)候出現(xiàn)幻讀,也就是出現(xiàn)兩條或者更多num=3這樣查詢(xún)條件的數(shù)據(jù)。
Mysql在滿足where條件的情況下,給(1,3]U[3,5)區(qū)間加上了鎖不允許插入num=3的數(shù)據(jù)行,這樣就解決了幻讀。
這里拋出幾種情況接著來(lái)測(cè)試間隙鎖。主鍵索引(唯一索引)是否會(huì)加上間隙鎖呢?范圍查詢(xún)是否會(huì)加上間隙鎖?使用不存在的檢索條件是否會(huì)加上間隙鎖?
先來(lái)說(shuō)說(shuō):「主鍵索引(唯一索引)是否會(huì)加上間隙鎖呢?」
因?yàn)橹麈I索引具有唯一性,不允許出現(xiàn)重復(fù),那么當(dāng)進(jìn)行等值查詢(xún)的時(shí)候id=3,只能有且只有一條數(shù)據(jù),是不可能再出現(xiàn)id=3的第二條數(shù)據(jù)。
因此它只要鎖定這條數(shù)據(jù)(鎖定索引),在下次查詢(xún)當(dāng)前讀的時(shí)候不會(huì)被刪除、或者更新id=3的數(shù)據(jù)行,也就保證了數(shù)據(jù)的一致性,所以主鍵索引由于他的唯一性的原因,是不需要加間隙鎖的。
再來(lái)說(shuō)說(shuō)第二個(gè)問(wèn)題:「范圍查詢(xún)是否會(huì)加上間隙鎖?」
直接在session1中執(zhí)行下面的sql語(yǔ)句,并在session2中在這個(gè)num>=3的查詢(xún)條件內(nèi)和外新增數(shù)據(jù):
select * from employee where num>=3 for update;
insert into employee values(6,'ceshi',5000,2); // 程序出現(xiàn)等待
insert into employee values(7,'ceshi',5000,4); // 程序出現(xiàn)等待
insert into employee values(8,'ceshi',5000,1); // 新增數(shù)據(jù)成功
我們來(lái)分析以下原理:?jiǎn)尾樵?xún)num>=3的時(shí)候,在現(xiàn)有的employee表中滿足條件的數(shù)據(jù)行,如下所示:
| id | num |
|---|---|
| 3 | 3 |
| 4 | 5 |
| 5 | 6 |
那么在設(shè)計(jì)者的角度出發(fā),我為了解決幻讀的現(xiàn)象:在num>=3的條件下是必須加上間隙鎖的。
而在小于num=3中,下一條數(shù)據(jù)行就是num=1了,為了防止在(1,3]的范圍中加入了num=3的數(shù)據(jù)行,所以也給這個(gè)間隙加上了鎖,這就是添加num=2數(shù)據(jù)行出現(xiàn)等待的原因。
最后來(lái)說(shuō)一說(shuō):「使用不存在的檢索條件是否會(huì)加上間隙鎖?」
假如是查詢(xún)num>=8的數(shù)據(jù)行呢?因?yàn)閑mployee表并不存在中num=8的數(shù)據(jù)行,num最大num=6,所以為了解決幻讀(6,8]與num>=8也會(huì)加上鎖。
說(shuō)到這里我相信很多人已經(jīng)對(duì)間隙鎖有了清晰和深入的認(rèn)識(shí),可以說(shuō)是精通了,又可以和面試官互扯了。
假如你是第一次接觸Mysql的鎖機(jī)制,第一次肯定是懵的,建議多認(rèn)真的看幾遍,跟著案例敲一下自己深刻的去體會(huì),慢慢的就懂了。
死鎖
死鎖在InnoDB中才會(huì)出現(xiàn)死鎖,MyISAM是不會(huì)出現(xiàn)死鎖,因?yàn)镸yISAM支持的是表鎖,一次性獲取了所有的鎖,其它的線程只能排隊(duì)等候。
而InnoDB默認(rèn)支持行鎖,獲取鎖是分步的,并不是一次性獲取所有的鎖,因此在鎖競(jìng)爭(zhēng)的時(shí)候就會(huì)出現(xiàn)死鎖的情況。
雖然InnoDB會(huì)出現(xiàn)死鎖,但是并不影響InnoDB成為最受歡迎的存儲(chǔ)引擎,MyISAM可以理解為串行化操作,讀寫(xiě)有序,因此支持的并發(fā)性能低下。
死鎖案例一
舉一個(gè)例子,現(xiàn)在數(shù)據(jù)庫(kù)表employee中六條數(shù)據(jù),如下所示:
其中name=ldc的有兩條數(shù)據(jù),并且name字段為普通索引,分別是id=2和id=3的數(shù)據(jù)行,現(xiàn)在假設(shè)有兩個(gè)事務(wù)分別執(zhí)行下面的兩條sql語(yǔ)句:
// session1執(zhí)行
update employee set num = 2 where name ='ldc';
// session2執(zhí)行
select * from employee where id = 2 or id =3;
其中session1執(zhí)行的sql獲取的數(shù)據(jù)行是兩條數(shù)據(jù),假設(shè)先獲取到第一個(gè)id=2的數(shù)據(jù)行,然后cpu的時(shí)間分配給了另一個(gè)事務(wù),另一個(gè)事務(wù)執(zhí)行查詢(xún)操作獲取了第二行數(shù)據(jù)也就是id=3的數(shù)據(jù)行。
當(dāng)事務(wù)2繼續(xù)執(zhí)行的時(shí)候獲取到id=3的數(shù)據(jù)行,鎖定了id=3的數(shù)據(jù)行,此時(shí)cpu又將時(shí)間分配給了第一個(gè)事務(wù),第一個(gè)事務(wù)執(zhí)行準(zhǔn)備獲取第二行數(shù)據(jù)的鎖,發(fā)現(xiàn)已經(jīng)被其他事務(wù)獲取了,它就處于等待的狀態(tài)。
當(dāng)cpu把時(shí)間有分配給了第二個(gè)事務(wù),第二個(gè)事務(wù)準(zhǔn)備獲取第一行數(shù)據(jù)的鎖發(fā)現(xiàn)已經(jīng)被第一個(gè)事務(wù)獲取了鎖,這樣就行了死鎖,兩個(gè)事務(wù)彼此之間相互等待。
死鎖案例二
第二種死鎖情況就是當(dāng)一個(gè)事務(wù)開(kāi)始并且update一條id=1的數(shù)據(jù)行時(shí),成功獲取到寫(xiě)鎖,此時(shí)另一個(gè)事務(wù)執(zhí)行也update另一條id=2的數(shù)據(jù)行時(shí),也成功獲取到寫(xiě)鎖(id為主鍵)。
此時(shí)cpu將時(shí)間分配給了事務(wù)一,事務(wù)一接著也是update id=2的數(shù)據(jù)行,因?yàn)槭聞?wù)二已經(jīng)獲取到id=2數(shù)據(jù)行的鎖,所以事務(wù)已處于等待狀態(tài)。
事務(wù)二有獲取到了時(shí)間,像執(zhí)行update id=1的數(shù)據(jù)行,但是此時(shí)id=1的鎖被事務(wù)一獲取到了,事務(wù)二也處于等待的狀態(tài),因此形成了死鎖。
| session1 | session2 |
|---|---|
| begin;update t set name='測(cè)試' where id=1; | begin |
| update t set name='測(cè)試' where id=2; | |
| update t set name='測(cè)試' where id=2; | |
| 等待..... | update t set name='測(cè)試' where id=1; |
| 等待..... | 等待...... |
死鎖的解決方案
首先要解決死鎖問(wèn)題,在程序的設(shè)計(jì)上,當(dāng)發(fā)現(xiàn)程序有高并發(fā)的訪問(wèn)某一個(gè)表時(shí),盡量對(duì)該表的執(zhí)行操作串行化,或者鎖升級(jí),一次性獲取所有的鎖資源。
然后也可以設(shè)置參數(shù)innodb_lock_wait_timeout,超時(shí)時(shí)間,并且將參數(shù)innodb_deadlock_detect 打開(kāi),當(dāng)發(fā)現(xiàn)死鎖的時(shí)候,自動(dòng)回滾其中的某一個(gè)事務(wù)。
總結(jié)
上面詳細(xì)的介紹了MyISAM和InnoDB兩種存儲(chǔ)引擎的鎖機(jī)制的實(shí)現(xiàn),并進(jìn)行了測(cè)試。
MyISAM的表鎖分為兩種模式:「共享讀鎖」和「排它寫(xiě)鎖」。獲取的讀鎖的線程對(duì)該數(shù)據(jù)行只能讀,不能修改,其它線程也只能對(duì)該數(shù)據(jù)行加讀鎖。
獲取到寫(xiě)鎖的線程對(duì)該數(shù)據(jù)行既能讀也能寫(xiě),對(duì)其他線程對(duì)該數(shù)據(jù)行的讀寫(xiě)具有排它性。
MyISAM中默認(rèn)寫(xiě)優(yōu)先于去操作,因此MyISAM一般不適合運(yùn)用于大量讀寫(xiě)操作的程序中。
InnoDB的行鎖雖然會(huì)出現(xiàn)死鎖的可能,但是InnoDB的支持的并發(fā)性能比MyISAM好,行鎖的粒度最小,一定的方法和措施可以解決死鎖的發(fā)生,極大的發(fā)揮InnoDB的性能。
InnoDB中引入了間隙鎖的概念來(lái)決解出現(xiàn)幻讀的問(wèn)題,也引入事務(wù)的特性,通過(guò)事務(wù)的四種隔離級(jí)別,來(lái)降低鎖沖突,提高并發(fā)性能。
【文章參考】
[1] https://www.cnblogs.com/leedaily/p/8378779.html
[2] https://blog.csdn.net/qq_38238296/article/details/88362999
特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒(méi)關(guān)注的小伙伴,可以長(zhǎng)按關(guān)注一下:
長(zhǎng)按訂閱更多精彩▼
如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!





