Java程序中解決數(shù)據(jù)庫(kù)超時(shí)與死鎖

字號(hào):

簡(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ù)據(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)程訪(fǎng)問(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è)用戶(hù)訪(fǎng)問(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ù)讀取NoNoNoNo
    讀取穩(wěn)定性NoNoNoYes
    光標(biāo)穩(wěn)定性NoNoYesYes
    未提交的讀7NoYesYesYes
    表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(用戶(hù)未提交的讀)。
    3、 盡可能關(guān)閉所有光標(biāo)。
    4、 有一個(gè)正確的提交策略。確保程序不再使用資源時(shí)就立即釋放它。
    如何處理死鎖與超時(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í)間。