數(shù)據(jù)庫是一個多用戶使用的共享資源,當(dāng)多個用戶并發(fā)地存取數(shù)據(jù)時,在數(shù)據(jù)庫中就會產(chǎn)生多個事務(wù)同時存取同一數(shù)據(jù)的情況陨簇。若對并發(fā)操作不加控制就可能會讀取和存儲不正確的數(shù)據(jù)贯吓,破壞數(shù)據(jù)庫的一致性。加鎖是實現(xiàn)數(shù)據(jù)庫并發(fā)控制的一個非常重要的技術(shù)买优。在實際應(yīng)用中經(jīng)常會遇到的與鎖相關(guān)的異常情況,當(dāng)兩個事務(wù)需要一組有沖突的鎖,而不能將事務(wù)繼續(xù)下去的話楚堤,就會出現(xiàn)死鎖,嚴重影響應(yīng)用的正常執(zhí)行含懊。
在數(shù)據(jù)庫中有兩種基本的鎖類型:排它鎖(Exclusive Locks身冬,即X鎖)和共享鎖(Share Locks,即S鎖)岔乔。當(dāng)數(shù)據(jù)對象被加上排它鎖時酥筝,其他的事務(wù)不能對它讀取和修改。加了共享鎖的數(shù)據(jù)對象可以被其他事務(wù)讀取雏门,但不能修改嘿歌。數(shù)據(jù)庫利用這兩種基本的鎖類型來對數(shù)據(jù)庫的事務(wù)進行并發(fā)控制掸掏。
下面總結(jié)下這兩種鎖造成的常見的死鎖情況與解決方案:
一. 事務(wù)之間對資源訪問順序的交替
出現(xiàn)原因:
一個用戶A 訪問表A(鎖住了表A),然后又訪問表B宙帝;另一個用戶B 訪問表B(鎖住了表B)丧凤,然后企圖訪問表A;這時用戶A由于用戶B已經(jīng)鎖住表B步脓,它必須等待用戶B釋放表B才能繼續(xù)愿待,同樣用戶B要等用戶A釋放表A才能繼續(xù),這就死鎖就產(chǎn)生了靴患。
解決方法:
這種死鎖比較常見仍侥,是由于程序的BUG產(chǎn)生的,除了調(diào)整的程序的邏輯沒有其它的辦法鸳君。仔細分析程序的邏輯农渊,對于數(shù)據(jù)庫的多表操作時,盡量按照相同的順序進行處理或颊,盡量避免同時鎖定兩個資源砸紊,如操作A和B兩張表時,總是按先A后B的順序處理囱挑, 必須同時鎖定兩個資源時批糟,要保證在任何時刻都應(yīng)該按照相同的順序來鎖定資源。
二. 并發(fā)修改同一記錄
出現(xiàn)原因:
用戶A查詢一條紀(jì)錄看铆,然后修改該條紀(jì)錄徽鼎;這時用戶B修改該條紀(jì)錄,這時用戶A的事務(wù)里鎖的性質(zhì)由查詢的共享鎖企圖上升到獨占鎖弹惦,而用戶B里的獨占鎖由于A有共享鎖存在所以必須等A釋放掉共享鎖否淤,而A由于B的獨占鎖而無法上升的獨占鎖也就不可能釋放共享鎖,于是出現(xiàn)了死鎖棠隐。這種死鎖由于比較隱蔽石抡,但在稍大點的項目中經(jīng)常發(fā)生。
一般更新模式由一個事務(wù)組成助泽,此事務(wù)讀取記錄啰扛,獲取資源(頁或行)的共享 (S) 鎖,然后修改行嗡贺,此操作要求鎖轉(zhuǎn)換為排它 (X) 鎖隐解。如果兩個事務(wù)獲得了資源上的共享模式鎖,然后試圖同時更新數(shù)據(jù)诫睬,則一個事務(wù)嘗試將鎖轉(zhuǎn)換為排它 (X) 鎖煞茫。共享模式到排它鎖的轉(zhuǎn)換必須等待一段時間,因為一個事務(wù)的排它鎖與其它事務(wù)的共享模式鎖不兼容;發(fā)生鎖等待续徽。第二個事務(wù)試圖獲取排它 (X) 鎖以進行更新蚓曼。由于兩個事務(wù)都要轉(zhuǎn)換為排它 (X) 鎖,并且每個事務(wù)都等待另一個事務(wù)釋放共享模式鎖钦扭,因此發(fā)生死鎖纫版。
解決方法:
a. 使用樂觀鎖進行控制。樂觀鎖大多是基于數(shù)據(jù)版本(Version)記錄機制實現(xiàn)客情。即為數(shù)據(jù)增加一個版本標(biāo)識捎琐,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個“version”字段來實現(xiàn)裹匙。讀取出數(shù)據(jù)時,將此版本號一同讀出末秃,之后更新時概页,對此版本號加一。此時练慕,將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應(yīng)記錄的當(dāng)前版本信息進行比對惰匙,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫表當(dāng)前版本號,則予以更新铃将,否則認為是過期數(shù)據(jù)项鬼。樂觀鎖機制避免了長事務(wù)中的數(shù)據(jù)庫加鎖開銷(用戶A和用戶B操作過程中,都沒有對數(shù)據(jù)庫數(shù)據(jù)加鎖)劲阎,大大提升了大并發(fā)量下的系統(tǒng)整體性能表現(xiàn)绘盟。Hibernate 在其數(shù)據(jù)訪問引擎中內(nèi)置了樂觀鎖實現(xiàn)。需要注意的是悯仙,由于樂觀鎖機制是在我們的系統(tǒng)中實現(xiàn)龄毡,來自外部系統(tǒng)的用戶更新操作不受我們系統(tǒng)的控制,因此可能會造成臟數(shù)據(jù)被更新到數(shù)據(jù)庫中锡垄。
b. 使用悲觀鎖進行控制沦零。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機制實現(xiàn),如Oracle的Select … for update語句货岭,以保證操作最大程度的獨占性路操。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對長事務(wù)而言千贯,這樣的開銷往往無法承受屯仗。如一個金融系統(tǒng),當(dāng)某個操作員讀取用戶的數(shù)據(jù)搔谴,并在讀出的用戶數(shù)據(jù)的基礎(chǔ)上進行修改時(如更改用戶賬戶余額)祭钉,如果采用悲觀鎖機制,也就意味著整個操作過程中(從操作員讀出數(shù)據(jù)己沛、開始修改直至提交修改結(jié)果的全過程慌核,甚至還包括操作員中途去煮咖啡的時間)距境,數(shù)據(jù)庫記錄始終處于加鎖狀態(tài),可以想見垮卓,如果面對成百上千個并發(fā)垫桂,這樣的情況將導(dǎo)致災(zāi)難性的后果。所以粟按,采用悲觀鎖進行控制時一定要考慮清楚诬滩。
c. SqlServer可支持更新鎖
為解決死鎖,SqlServer引入更新鎖,它有如下特征:
(1) 加鎖的條件:當(dāng)一個事務(wù)執(zhí)行update語句時灭将,數(shù)據(jù)庫系統(tǒng)會先為事務(wù)分配一把更新鎖疼鸟。
(2) 解鎖的條件:當(dāng)讀取數(shù)據(jù)完畢,執(zhí)行更新操作時庙曙,會把更新鎖升級為獨占鎖空镜。
(3) 與其他鎖的兼容性:更新鎖與共享鎖是兼容的,也就是說捌朴,一個資源可以同時放置更新鎖和共享鎖吴攒,但是最多放置一把更新鎖。這樣砂蔽,當(dāng)多個事務(wù)更新相同的數(shù)據(jù)時洼怔,只有一個事務(wù)能獲得更新鎖,然后再把更新鎖升級為獨占鎖左驾,其他事務(wù)必須等到前一個事務(wù)結(jié)束后镣隶,才能獲取得更新鎖,這就避免了死鎖诡右。
(4) 并發(fā)性能:允許多個事務(wù)同時讀鎖定的資源矾缓,但不允許其他事務(wù)修改它。
例子如下:
T1:
begin tran
select * from table(updlock) (加更新鎖)
update table setcolumn1='hello'
T2:
begin tran
select * from table(updlock)
update table setcolumn1='world'
更新鎖的意思是:“我現(xiàn)在只想讀稻爬,你們別人也可以讀嗜闻,但我將來可能會做更新操作,我已經(jīng)獲取了從共享鎖(用來讀)到排他鎖(用來更新)的資格”桅锄。一個事物只能有一個更新鎖獲此資格琉雳。
T1執(zhí)行select,加更新鎖友瘤。
T2運行翠肘,準(zhǔn)備加更新鎖,但發(fā)現(xiàn)已經(jīng)有一個更新鎖在那兒了辫秧,只好等束倍。
當(dāng)后來有user3、user4…需要查詢table表中的數(shù)據(jù)時,并不會因為T1的select在執(zhí)行就被阻塞绪妹,照樣能查詢,提高了效率甥桂。
三. 索引不當(dāng)導(dǎo)致全表掃描
出現(xiàn)原因:
如果在事務(wù)中執(zhí)行了一條不滿足條件的語句,執(zhí)行全表掃描邮旷,把行級鎖上升為表級鎖黄选,多個這樣的事務(wù)執(zhí)行后,就很容易產(chǎn)生死鎖和阻塞婶肩。類似的情況還有當(dāng)表中的數(shù)據(jù)量非常龐大而索引建的過少或不合適的時候办陷,使得經(jīng)常發(fā)生全表掃描,最終應(yīng)用系統(tǒng)會越來越慢律歼,最終發(fā)生阻塞或死鎖民镜。
解決方法:
SQL語句中不要使用太復(fù)雜的關(guān)聯(lián)多表的查詢;使用“執(zhí)行計劃”對SQL語句進行分析险毁,對于有全表掃描的SQL語句制圈,建立相應(yīng)的索引進行優(yōu)化。
四.事務(wù)封鎖范圍大且相互等待
https://blog.csdn.net/qq_16681169/article/details/73359670
本文轉(zhuǎn)載至:https://blog.csdn.net/qq_16681169/article/details/74784193