MySQL之鎖總結(jié)

11.jpg

不少人在開發(fā)的時(shí)候扼倘,應(yīng)該很少會注意到這些鎖的問題按价,也很少會給程序加鎖(除了庫存這些對數(shù)量準(zhǔn)確性要求極高的情況下)

一般也就聽過常說的樂觀鎖和悲觀鎖赚爵,了解過基本的含義之后就沒了~~~

即使我們不會這些鎖知識瓢湃,我們的程序在一般情況下還是可以跑得好好的矢炼。因?yàn)檫@些鎖數(shù)據(jù)庫隱式幫我們加了。

  • 對于UPDATE慷垮,DELETE揖闸,INSERT語句,InnoDB會自動給涉及到的數(shù)據(jù)集加排它鎖料身。

  • MyISAM在執(zhí)行SELECT之前汤纸,會自動給涉及到的所有表加讀鎖,在執(zhí)行更新操作(UPDATE芹血,DELETE贮泞,INSERT)前楞慈,會自動給涉及到的表加寫鎖,這個(gè)過程不需要用戶干預(yù)啃擦。

只有某些特定的情況才需要我們手動加鎖囊蓝,那么我們學(xué)習(xí)鎖的目的是什么呢?當(dāng)然是為了裝逼令蛉,人生不裝逼聚霜,那還有什么意義。


一珠叔、鎖簡單介紹

從鎖的粒度蝎宇,可以分為兩大類:

  • 表鎖:開銷小,加鎖快祷安,不會出現(xiàn)死鎖夫啊,鎖定力度大,發(fā)生鎖沖突的概率高辆憔,并發(fā)度低。

  • 行鎖:開銷大报嵌,加鎖慢虱咧,會出現(xiàn)死鎖,鎖定力度小锚国,發(fā)生鎖沖突的概率低腕巡,并發(fā)讀高。

不同的存儲引擎支持的鎖力度是不一樣的血筑。

  • InnoDB支持表鎖和行鎖绘沉。

  • MyISAM只支持表鎖。

InnoDB只有通過索引條件檢索數(shù)據(jù)才使用行級鎖豺总,否則车伞,InnoDB將使用表鎖。也就是說喻喳,InnoDB的行鎖是基于索引的另玖。

表鎖下又分為兩種模式:

  • 表讀鎖(Table Read Lock)
  • 表寫鎖(Table Write Lock)

從下圖可以清晰看到,在表讀鎖和表寫鎖的環(huán)境下:讀讀不阻塞表伦,讀寫阻塞谦去,寫寫阻塞!
讀讀不阻塞:當(dāng)前用戶在讀數(shù)據(jù)蹦哼,其他的用戶也在讀數(shù)據(jù)鳄哭,不會加鎖。
讀寫阻塞:當(dāng)前用戶在讀數(shù)據(jù)纲熏,其他的用戶不能修改當(dāng)前用戶讀的數(shù)據(jù)妆丘,會加鎖锄俄!
寫寫阻塞:當(dāng)前用戶在修改數(shù)據(jù),其他的用戶不能修改當(dāng)前用戶正在修改的數(shù)據(jù)飘痛,會加鎖珊膜!

12.jpg

從上面已經(jīng)看到了:讀鎖和寫鎖是互斥的,讀寫操作是串行宣脉。

  • 如果某個(gè)進(jìn)程想要獲取讀鎖车柠,同時(shí)另外一個(gè)進(jìn)程想要獲取寫鎖。在mysql里邊塑猖,寫鎖是優(yōu)先于讀鎖的竹祷!
  • 寫鎖和讀鎖優(yōu)先級的問題可以通過參數(shù)調(diào)節(jié):max_write_lock_count和low-priority-updates

值得注意的是:

  • MyISAM可以支持查詢和插入操作的并發(fā)進(jìn)行⊙蚬叮可以通過系統(tǒng)變量concurrent_insert來指定哪種模式塑陵,在MyISAM中它默認(rèn)是:如果MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM允許在一個(gè)進(jìn)程讀表的同時(shí)蜡励,另一個(gè)進(jìn)程從表尾插入記錄令花。
  • 但是InnoDB存儲引擎是不支持的!

二凉倚、MySQL的事務(wù)隔離級別

在這之前兼都,先了解一下MySQL的事務(wù)隔離級別以及并發(fā)帶來的問題定续。

事務(wù)的并發(fā)問題

1后控、臟讀:事務(wù)A讀取了事務(wù)B更新的數(shù)據(jù),然后B回滾操作堤瘤,那么A讀取到的數(shù)據(jù)是臟數(shù)據(jù)

2杏糙、不可重復(fù)讀:事務(wù) A 多次讀取同一數(shù)據(jù)慎王,事務(wù) B 在事務(wù)A多次讀取的過程中,對數(shù)據(jù)作了更新并提交宏侍,導(dǎo)致事務(wù)A多次讀取同一數(shù)據(jù)時(shí)赖淤,結(jié)果 不一致。

3谅河、幻讀:系統(tǒng)管理員A將數(shù)據(jù)庫中所有學(xué)生的成績從具體分?jǐn)?shù)改為ABCDE等級漫蛔,但是系統(tǒng)管理員B就在這個(gè)時(shí)候插入了一條具體分?jǐn)?shù)的記錄,當(dāng)系統(tǒng)管理員A改結(jié)束后發(fā)現(xiàn)還有一條記錄沒有改過來旧蛾,就好像發(fā)生了幻覺一樣莽龟,這就叫幻讀。

小結(jié):不可重復(fù)讀和幻讀很容易混淆锨天,不可重復(fù)讀側(cè)重于修改毯盈,幻讀側(cè)重于新增或刪除。解決不可重復(fù)讀的問題只需鎖住滿足條件的行病袄,解決幻讀需要鎖表

MySQL事務(wù)隔離級別

事務(wù)隔離級別 臟讀 不可重復(fù)讀 幻讀
讀未提交(read-uncommitted)
讀已提交(read-committed)
可重復(fù)讀(repeatable-read)
串行化(serializable)

MySQL的默認(rèn)隔離級別是可重復(fù)讀搂赋,可以通過下面的命令查看

select @@tx_isolation;

隔離級別越高赘阀,越能保證數(shù)據(jù)的完整性和一致性,但是對并發(fā)性能的影響也越大脑奠,魚和熊掌不可兼得啊基公。對于多數(shù)應(yīng)用程序,可以優(yōu)先考慮把數(shù)據(jù)庫系統(tǒng)的隔離級別設(shè)為Read Committed宋欺,它能夠避免臟讀取轰豆,而且具有較好的并發(fā)性能。盡管它會導(dǎo)致不可重復(fù)讀齿诞、幻讀這些并發(fā)問題酸休,在可能出現(xiàn)這類問題的個(gè)別場合,可以由應(yīng)用程序采用悲觀鎖或樂觀鎖來控制祷杈。

可以通過下面的命令設(shè)置事務(wù)隔離級別

set session transaction isolation level xxxx;


三斑司、樂觀鎖和悲觀鎖

無論是Read committed還是Repeatable read隔離級別,都是為了解決讀寫沖突的問題但汞。

單純在Repeatable read隔離級別下我們來考慮一個(gè)問題:


13.jpg

此時(shí)宿刮,用戶李四的操作就丟失掉了

丟失更新:一個(gè)事務(wù)的更新覆蓋了其它事務(wù)的更新結(jié)果。

解決的方法:

  • 使用Serializable隔離級別私蕾,事務(wù)是串行執(zhí)行的僵缺!
  • 樂觀鎖
  • 悲觀鎖

樂觀鎖是一種思想,具體實(shí)現(xiàn)是是目,表中有一個(gè)版本字段,第一次讀的時(shí)候标捺,獲取到這個(gè)字段懊纳。處理完業(yè)務(wù)邏輯開始更新的時(shí)候,需要再次查看該字段的值是否和第一次的一樣亡容。如果一樣更新嗤疯,反之拒絕。之所以叫樂觀闺兢,因?yàn)檫@個(gè)模式?jīng)]有從數(shù)據(jù)庫加鎖茂缚,等到更新的時(shí)候再判斷是否可以更新。

悲觀鎖是數(shù)據(jù)庫層面加鎖屋谭,都會阻塞去等待鎖脚囊。

1.悲觀鎖

所以,按照上面的例子桐磁。我們使用悲觀鎖的話其實(shí)很簡單(手動加行鎖就行了):

select * from xxxx for update

在select 語句后邊加了 for update相當(dāng)于加了排它鎖(寫鎖)悔耘,加了寫鎖以后,其他的事務(wù)就不能對它修改了我擂!需要等待當(dāng)前事務(wù)修改完之后才可以修改衬以。也就是說缓艳,如果張三使用select ... for update,李四就無法對該條記錄修改了看峻。

2.樂觀鎖

樂觀鎖不是數(shù)據(jù)庫層面上的鎖阶淘,不需要自己手動去加的鎖。一般我們添加一個(gè)版本字段來實(shí)現(xiàn):

具體過程是這樣的:

張三select * from table --->會查詢出記錄出來互妓,同時(shí)會有一個(gè)version字段

name age version
male 25 1

李四select * from table --->會查詢出記錄出來溪窒,同時(shí)會有一個(gè)version字段

name age version
male 25 1

李四對這條記錄做修改

update A set age=30,version=version+1 where name=#{name} and version=#{version}

判斷之前查詢到的version與現(xiàn)在的數(shù)據(jù)的version進(jìn)行比較,同時(shí)會更新version字段车猬。

此時(shí)數(shù)據(jù)庫記錄如下:

name age version
male 30 2

張三也對這條記錄修改:update A set name=female,version=version+1 where name=#{name} and version=#{version}霉猛,但失敗了!因?yàn)楫?dāng)前數(shù)據(jù)庫中的版本跟查詢出來的版本不一致珠闰!


14.jpg


四惜浅、間隙鎖GPA

當(dāng)我們用范圍條件檢索數(shù)據(jù)而不是相等條件檢索數(shù)據(jù),并請求共享或排他鎖時(shí)伏嗜,InnoDB會給符合范圍條件的已有數(shù)據(jù)記錄的索引項(xiàng)加鎖坛悉;對于鍵值在條件范圍內(nèi)但并不存在的記錄,叫做“間隙(GAP)”承绸。InnoDB也會對這個(gè)“間隙”加鎖裸影,這種鎖機(jī)制就是所謂的間隙鎖。

值得注意的是:間隙鎖只會在Repeatable read隔離級別下使用

例子:假如emp表中只有101條記錄军熏,其empid的值分別是1,2,...,100,101

Select * from emp where empid > 100 for update;

上面是一個(gè)范圍查詢轩猩,InnoDB不僅會對符合條件的empid值為101的記錄加鎖,也會對empid大于101(這些記錄并不存在)的“間隙”加鎖荡澎。

InnoDB使用間隙鎖的目的:

  • 為了防止幻讀:在一個(gè)事務(wù)未提交前均践,其他并發(fā)事務(wù)不能插入滿足其鎖定條件的任何記錄,也就是不允許出現(xiàn)幻讀摩幔。


五彤委、死鎖

并發(fā)的問題就少不了死鎖,在MySQL中同樣會存在死鎖的問題或衡。

但一般來說MySQL通過回滾幫我們解決了不少死鎖的問題了焦影,但死鎖是無法完全避免的,可以通過以下的經(jīng)驗(yàn)參考封断,來盡可能少遇到死鎖:

(1)以固定的順序訪問表和行斯辰。比如對兩個(gè)job批量更新的情形,簡單方法是對id列表先排序坡疼,后執(zhí)行椒涯,這樣就避免了交叉等待鎖的情形;將兩個(gè)事務(wù)的sql順序調(diào)整為一致,也能避免死鎖废岂。

(2)大事務(wù)拆小祖搓。大事務(wù)更傾向于死鎖,如果業(yè)務(wù)允許湖苞,將大事務(wù)拆小拯欧。

(3)在同一個(gè)事務(wù)中,盡可能做到一次鎖定所需要的所有資源财骨,減少死鎖概率镐作。

(4)降低隔離級別。如果業(yè)務(wù)允許隆箩,將隔離級別調(diào)低也是較好的選擇该贾,比如將隔離級別從RR調(diào)整為RC,可以避免掉很多因?yàn)間ap鎖造成的死鎖捌臊。

(5)為表添加合理的索引杨蛋。可以看到如果不走索引將會為表的每一行記錄添加上鎖理澎,死鎖的概率大大增大逞力。


六、總結(jié)

上面說了一大堆關(guān)于MySQL數(shù)據(jù)庫鎖的東西糠爬,現(xiàn)在來簡單總結(jié)一下寇荧。

表鎖其實(shí)我們程序員是很少關(guān)心它的:

  • 在MyISAM存儲引擎中,當(dāng)執(zhí)行SQL語句的時(shí)候是自動加表鎖的执隧。
  • 在InnoDB存儲引擎中揩抡,如果沒有使用索引,表鎖也是自動加的镀琉。

現(xiàn)在我們大多數(shù)使用MySQL都是使用InnoDB峦嗤,InnoDB支持行鎖:

  • 共享鎖--讀鎖--S鎖
  • 排它鎖--寫鎖--X鎖

在默認(rèn)的情況下,select是不加任何行鎖的~事務(wù)可以通過以下語句顯示給記錄集加共享鎖或排他鎖滚粟。

// 共享鎖(S)
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE寻仗。
// 排他鎖(X)
SELECT * FROM table_name WHERE ... FOR UPDATE刃泌。

InnoDB基于行鎖還實(shí)現(xiàn)了MVCC多版本并發(fā)控制凡壤,MVCC在隔離級別下的Read committed和Repeatable read下工作。MVCC能夠?qū)崿F(xiàn)讀寫不阻塞耙替!

InnoDB實(shí)現(xiàn)的Repeatable read隔離級別配合GAP間隙鎖已經(jīng)避免了幻讀亚侠!

  • 樂觀鎖其實(shí)是一種思想,正如其名:認(rèn)為不會鎖定的情況下去更新數(shù)據(jù)俗扇,如果發(fā)現(xiàn)不對勁硝烂,才不更新(回滾)。在數(shù)據(jù)庫中往往添加一個(gè)version字段來實(shí)現(xiàn)铜幽。
  • 悲觀鎖用的就是數(shù)據(jù)庫的行鎖滞谢,認(rèn)為數(shù)據(jù)庫會發(fā)生并發(fā)沖突串稀,直接上來就把數(shù)據(jù)鎖住,其他事務(wù)不能修改狮杨,直至提交了當(dāng)前事務(wù)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末母截,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子橄教,更是在濱河造成了極大的恐慌清寇,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件护蝶,死亡現(xiàn)場離奇詭異华烟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)持灰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門盔夜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人搅方,你說我怎么就攤上這事比吭。” “怎么了姨涡?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵衩藤,是天一觀的道長。 經(jīng)常有香客問我涛漂,道長赏表,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任匈仗,我火速辦了婚禮瓢剿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悠轩。我一直安慰自己间狂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布火架。 她就那樣靜靜地躺著鉴象,像睡著了一般。 火紅的嫁衣襯著肌膚如雪何鸡。 梳的紋絲不亂的頭發(fā)上纺弊,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機(jī)與錄音骡男,去河邊找鬼淆游。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的犹菱。 我是一名探鬼主播拾稳,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腊脱!你這毒婦竟也來了熊赖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤虑椎,失蹤者是張志新(化名)和其女友劉穎震鹉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捆姜,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡传趾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泥技。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浆兰。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖珊豹,靈堂內(nèi)的尸體忽然破棺而出簸呈,到底是詐尸還是另有隱情,我是刑警寧澤店茶,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布蜕便,位于F島的核電站,受9級特大地震影響贩幻,放射性物質(zhì)發(fā)生泄漏轿腺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一丛楚、第九天 我趴在偏房一處隱蔽的房頂上張望族壳。 院中可真熱鬧,春花似錦趣些、人聲如沸仿荆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拢操。三九已至,卻和暖如春功茴,著一層夾襖步出監(jiān)牢的瞬間庐冯,已是汗流浹背孽亲。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工坎穿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓玲昧,卻偏偏與公主長得像栖茉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子孵延,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容