在 MySQL 中,當(dāng)兩個(gè)或以上的事務(wù)相互持有和請(qǐng)求鎖,并形成一個(gè)循環(huán)的依賴(lài)關(guān)系,就會(huì)產(chǎn)生死鎖。針對(duì)我們的業(yè)務(wù)系統(tǒng),出現(xiàn)死鎖的直接結(jié)果就是系統(tǒng)卡頓、客戶(hù)找事兒,所以我們也在想盡全力的消除掉數(shù)據(jù)庫(kù)的死鎖。
下面就和云和數(shù)據(jù)小編一起看看死鎖的三種情況及解決辦法。
死鎖的第一種情況
一個(gè)用戶(hù)A 訪(fǎng)問(wèn)表A(鎖住了表A),然后又訪(fǎng)問(wèn)表B;另一個(gè)用戶(hù)B 訪(fǎng)問(wèn)表B(鎖住了表B),然后企圖訪(fǎng)問(wèn)表A;這時(shí)用戶(hù)A由于用戶(hù)B已經(jīng)鎖住表B,它必須等待用戶(hù)B釋放表B才能繼續(xù),同樣用戶(hù)B要等用戶(hù)A釋放表A才能繼續(xù),這就死鎖就產(chǎn)生了。
解決方法:
這種死鎖比較常見(jiàn),是由于程序的BUG產(chǎn)生的,除了調(diào)整的程序的邏輯沒(méi)有其它的辦法。仔細(xì)分析程序的邏輯,對(duì)于數(shù)據(jù)庫(kù)的多表操作時(shí),盡量按照相同的順序進(jìn) 行處理,盡量避免同時(shí)鎖定兩個(gè)資源,如操作A和B兩張表時(shí),總是按先A后B的順序處理, 必須同時(shí)鎖定兩個(gè)資源時(shí),要保證在任何時(shí)刻都應(yīng)該按照相同的順序來(lái)鎖定資源。
死鎖的第二種情況
用戶(hù)A查詢(xún)一條紀(jì)錄,然后修改該條紀(jì)錄;這時(shí)用戶(hù)B修改該條紀(jì)錄,這時(shí)用戶(hù)A的事務(wù)里鎖的性質(zhì)由查詢(xún)的共享鎖企圖上升到獨(dú)占鎖,而用戶(hù)B里的獨(dú)占鎖由于A(yíng) 有共享鎖存在所以必須等A釋放掉共享鎖,而A由于B的獨(dú)占鎖而無(wú)法上升的獨(dú)占鎖也就不可能釋放共享鎖,于是出現(xiàn)了死鎖。這種死鎖比較隱蔽,但在稍大點(diǎn)的項(xiàng) 目中經(jīng)常發(fā)生。如在某項(xiàng)目中,頁(yè)面上的按鈕點(diǎn)擊后,沒(méi)有使按鈕立刻失效,使得用戶(hù)會(huì)多次快速點(diǎn)擊同一按鈕,這樣同一段代碼對(duì)數(shù)據(jù)庫(kù)同一條記錄進(jìn)行多次操 作,很容易就出現(xiàn)這種死鎖的情況。
解決方法:
1、對(duì)于按鈕等控件,點(diǎn)擊后使其立刻失效,不讓用戶(hù)重復(fù)點(diǎn)擊,避免對(duì)同時(shí)對(duì)同一條記錄操作。
2、使用樂(lè)觀(guān)鎖進(jìn)行控制。樂(lè)觀(guān)鎖大多是基于數(shù)據(jù)版本(Version)記錄機(jī)制實(shí)現(xiàn)。即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),在基于數(shù)據(jù)庫(kù)表的版本解決方案中,一般是 通過(guò)為數(shù)據(jù)庫(kù)表增加一個(gè)“version”字段來(lái)實(shí)現(xiàn)。讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后更新時(shí),對(duì)此版本號(hào)加一。此時(shí),將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù) 據(jù)庫(kù)表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫(kù)表當(dāng)前版本號(hào),則予以更新,否則認(rèn)為是過(guò)期數(shù)據(jù)。樂(lè)觀(guān)鎖機(jī)制避免了長(zhǎng)事務(wù)中的數(shù)據(jù) 庫(kù)加鎖開(kāi)銷(xiāo)(用戶(hù)A和用戶(hù)B操作過(guò)程中,都沒(méi)有對(duì)數(shù)據(jù)庫(kù)數(shù)據(jù)加鎖),大大提升了大并發(fā)量下的系統(tǒng)整體性能表現(xiàn)。Hibernate 在其數(shù)據(jù)訪(fǎng)問(wèn)引擎中內(nèi)置了樂(lè)觀(guān)鎖實(shí)現(xiàn)。需要注意的是,由于樂(lè)觀(guān)鎖機(jī)制是在我們的系統(tǒng)中實(shí)現(xiàn),來(lái)自外部系統(tǒng)的用戶(hù)更新操作不受我們系統(tǒng)的控制,因此可能會(huì)造 成臟數(shù)據(jù)被更新到數(shù)據(jù)庫(kù)中。
3、使用悲觀(guān)鎖進(jìn)行控制。悲觀(guān)鎖大多數(shù)情況下依靠數(shù)據(jù)庫(kù)的鎖機(jī)制實(shí)現(xiàn),如Oracle的Select … for update語(yǔ)句,以保證操作最大程度的獨(dú)占性。但隨之而來(lái)的就是數(shù)據(jù)庫(kù)性能的大量開(kāi)銷(xiāo),特別是對(duì)長(zhǎng)事務(wù)而言,這樣的開(kāi)銷(xiāo)往往無(wú)法承受。如一個(gè)金融系統(tǒng), 當(dāng)某個(gè)操作員讀取用戶(hù)的數(shù)據(jù),并在讀出的用戶(hù)數(shù)據(jù)的基礎(chǔ)上進(jìn)行修改時(shí)(如更改用戶(hù)賬戶(hù)余額),如果采用悲觀(guān)鎖機(jī)制,也就意味著整個(gè)操作過(guò)程中(從操作員讀 出數(shù)據(jù)、開(kāi)始修改直至提交修改結(jié)果的全過(guò)程,甚至還包括操作員中途去煮咖啡的時(shí)間),數(shù)據(jù)庫(kù)記錄始終處于加鎖狀態(tài),可以想見(jiàn),如果面對(duì)成百上千個(gè)并發(fā),這 樣的情況將導(dǎo)致災(zāi)難性的后果。所以,采用悲觀(guān)鎖進(jìn)行控制時(shí)一定要考慮清楚。
死鎖的第三種情況
如果在事務(wù)中執(zhí)行了一條不滿(mǎn)足條件的update語(yǔ)句,則執(zhí)行全表掃描,把行級(jí)鎖上升為表級(jí)鎖,多個(gè)這樣的事務(wù)執(zhí)行后,就很容易產(chǎn)生死鎖和阻塞。類(lèi)似的情 況還有當(dāng)表中的數(shù)據(jù)量非常龐大而索引建的過(guò)少或不合適的時(shí)候,使得經(jīng)常發(fā)生全表掃描,最終應(yīng)用系統(tǒng)會(huì)越來(lái)越慢,最終發(fā)生阻塞或死鎖。
解決方法:
SQL語(yǔ)句中不要使用太復(fù)雜的關(guān)聯(lián)多表的查詢(xún);使用“執(zhí)行計(jì)劃”對(duì)SQL語(yǔ)句進(jìn)行分析,對(duì)于有全表掃描的SQL語(yǔ)句,建立相應(yīng)的索引進(jìn)行優(yōu)化。
總之,產(chǎn)生內(nèi)存溢出與鎖表都是由于代碼寫(xiě)的不好造成的,因此提高代碼的質(zhì)量是最根本的解決辦法。