在Java程序中處理數(shù)據(jù)庫(kù)超時(shí)與死鎖
簡(jiǎn)介:每個(gè)使用關(guān)系型數(shù)據(jù)庫(kù)的程序都可能遇到數(shù)據(jù)死鎖或不可用的情況,而這些情況需要在代碼中編程來(lái)解決,本文主要介紹與數(shù)據(jù)庫(kù)事務(wù)死鎖等情況相關(guān)的重試邏輯概念,此外,還會(huì)探討如何避免死鎖等問(wèn)題,文章以DB2(版本9)與Java為例進(jìn)行講解。下面就跟隨我一起去探索超時(shí)與死鎖的奧秘吧。
什么是數(shù)據(jù)庫(kù)鎖定與死鎖
鎖定(Locking)發(fā)生在當(dāng)一個(gè)事務(wù)獲得對(duì)某一資源的“鎖”時(shí),這時(shí),其他的事務(wù)就不能更改這個(gè)資源了,這種機(jī)制的存在是為了保證數(shù)據(jù)一致性;在設(shè)計(jì)與數(shù)據(jù)庫(kù)交互的程序時(shí),必須處理鎖與資源不可用的情況。鎖定是個(gè)比較復(fù)雜的概念,仔細(xì)說(shuō)起來(lái)可能又需要一大篇,所以在本文中,只把鎖定看作是一個(gè)臨時(shí)事件,這意味著如果一個(gè)資源被鎖定,它總會(huì)在以后某個(gè)時(shí)間被釋放。而死鎖發(fā)生在當(dāng)多個(gè)進(jìn)程訪問(wèn)同一數(shù)據(jù)庫(kù)時(shí),其中每個(gè)進(jìn)程擁有的鎖都是其他進(jìn)程所需的,由此造成每個(gè)進(jìn)程都無(wú)法繼續(xù)下去。
如何避免鎖
我們可利用事務(wù)型數(shù)據(jù)庫(kù)中的隔離級(jí)別機(jī)制來(lái)避免鎖的創(chuàng)建,正確地使用隔離級(jí)別可使程序處理更多的并發(fā)事件(如允許多個(gè)用戶訪問(wèn)數(shù)據(jù)),還能預(yù)防像丟失修改(Lost Update)、讀“臟”數(shù)據(jù)(Dirty Read)、不可重復(fù)讀(Nonrepeatable Read)及“虛”(Phantom)等問(wèn)題。
隔離級(jí)別 問(wèn)題現(xiàn)象
丟失修改 讀“臟”數(shù)據(jù) 不可重復(fù)讀 “虛”
可重復(fù)讀取 No No No No
讀取穩(wěn)定性 No No No Yes
光標(biāo)穩(wěn)定性 No No Yes Yes
未提交的讀 No Yes Yes Yes
表1:DB2的隔離級(jí)別與其對(duì)應(yīng)的問(wèn)題現(xiàn)象
在只讀模式中,就可以防止鎖定發(fā)生,而不用那些未提交只讀隔離級(jí)別的含糊語(yǔ)句。一條SQL語(yǔ)句當(dāng)使用了下列命令之一時(shí),就應(yīng)該考慮只讀模式了:
1、JOIN
2、SELECT DISTINCT
3、GROUP BY
4、ORDER BY
5、UNION
6、UNION ALL
7、SELECT
8、FOR FETCH ONLY (FOR READ ONLY)
9、SELECT FROM
如果包含上述任一命令,可以說(shuō)你的SQL語(yǔ)句有歧義性,因此,鎖可能就是造成其中資源問(wèn)題的源頭。
另外,以下是一些可降低鎖數(shù)目的建議:
1、 將CURRENTDATA設(shè)為NO。這條命令告訴DB2模糊光標(biāo)為只讀。
2、 在適當(dāng)?shù)臅r(shí)候,盡可能使用User Uncommitted Read(用戶未提交的讀)。
3、 盡可能關(guān)閉所有光標(biāo)。
4、 有一個(gè)正確的提交策略。確保程序不再使用資源時(shí)就立即釋放它。
#p#
如何處理死鎖與超時(shí)
在程序中使用重試邏輯,可處理以下三種SQL錯(cuò)誤代碼:
1、 904:返回這個(gè)代碼表示一條SQL語(yǔ)句是因?yàn)橐堰_(dá)到資源限度而結(jié)束的。程序中可提交或回滾更改,并執(zhí)行重試邏輯。
2、 911:程序收到這個(gè)SQL代碼,表示因?yàn)闆](méi)有為鎖列表分配足夠的內(nèi)存,現(xiàn)在已達(dá)到數(shù)據(jù)庫(kù)的最大鎖數(shù)目。
3、 912:程序收到這個(gè)SQL代碼,表示死鎖或超時(shí),依照904中的方法來(lái)解決。
以下是一段Java代碼,其捕捉返回的-911、-912、-904代碼,并進(jìn)行重試:
for (int i = 0; i < MAX_RETRY_ATTEMPTS; i++) {
//以下代碼模擬一次事務(wù)
try {
stmt = conn.createStatement();
System.out.println("Transaction started...");
stmt.executeUpdate("UPDATE 1..."); //SQL語(yǔ)句1
stmt.executeUpdate("UPDATE 2..."); // SQL語(yǔ)句2
stmt.executeUpdate("UPDATE 3..."); // SQL語(yǔ)句3
stmt.executeUpdate("UPDATE 3..."); // SQL語(yǔ)句4
//提交所有更改
conn.commit();
System.out.println("事務(wù)已完成。");
//確保只運(yùn)行了一次。
i = MAX_RETRY_ATTEMPTS;
} catch (SQLException e) {
/**
*如果返回的SQL代碼為-911,回滾會(huì)自動(dòng)完成,程序回滾至前一次的提交狀態(tài)。
*程序?qū)⑦M(jìn)行重試。
*/
if (-911 == e.getErrorCode()) {
//等待RETRY_WAIT_TIME
try {
Thread.sleep(RETRY_WAIT_TIME);
} catch (InterruptedException e1) {
//即使休眠被打斷,但仍要重試。
System.out.println("休眠被打斷。");
}
}
/**
*如果返回的SQL代碼為-912,表示死鎖及超時(shí)。
*如果是-904,代表已達(dá)到資源限度。
*在這種情況下,程序?qū)⒒貪L并進(jìn)行重試。
*/
else if (-912 == e.getErrorCode() || -904 == e.getErrorCode()) {
try {
//需要回滾
conn.rollback();
} catch (SQLException e1) {
System.out.println("無(wú)法回滾。"; color:black'> + e);
}
try {
//等待RETRY_WAIT_TIME
Thread.sleep(RETRY_WAIT_TIME);
} catch (InterruptedException e1) {
//即使休眠被打斷,但仍要重試。
System.out.println("休眠被打斷。" + e1);
}
} else {
//如果是其他錯(cuò)誤,就不進(jìn)行重試。
i = MAX_RETRY_ATTEMPTS;
System.out.println("有錯(cuò)誤發(fā)生,錯(cuò)誤代碼:"
+ e.getErrorCode() + " SQL狀態(tài):"
+ e.getSQLState() + "其他信息:" + e.getMessage());
}
從上面也可看到,程序?qū)λ梨i、超時(shí)、最大鎖數(shù)目將會(huì)進(jìn)行MAX_RETRY_ATTEMPTS次重試;其次,當(dāng)“最大鎖數(shù)目”的情況發(fā)生時(shí)(-911),程序不必手工進(jìn)行回滾,因?yàn)榇藭r(shí)的回滾是自動(dòng)完成的;最后,無(wú)論何時(shí)返回-911、-904、-912代碼,程序應(yīng)在下次重試前等待RETRY_WAIT_TIME一段時(shí)間。
以上就是我要為大家介紹的全部?jī)?nèi)容,這些內(nèi)容都是很實(shí)用的哦,掌握了這些在應(yīng)對(duì)超時(shí)和死鎖的問(wèn)題時(shí),您就能輕松應(yīng)對(duì)啦,還等什么,趕快來(lái)學(xué)習(xí)吧。
【編輯推薦】