1. 程式人生 > 其它 >在Java程式中處理資料庫超時與死鎖

在Java程式中處理資料庫超時與死鎖

簡介

  每個使用關係型資料庫的程式都可能遇到資料死鎖或不可用的情況,而這些情況需要在程式碼中程式設計來解決;本文主要介紹與資料庫事務死鎖等情況相關的重試邏輯概念,此外,還會探討如何避免死鎖等問題,文章以DB2(版本9)與Java為例進行講解。

  什麼是資料庫鎖定死鎖

  鎖定(Locking)發生在當一個事務獲得對某一資源的“鎖”時,這時,其他的事務就不能更改這個資源了,這種機制的存在是為了保證資料一致性;在設計與資料庫互動的程式時,必須處理鎖與資源不可用的情況。鎖定是個比較複雜的概念,仔細說起來可能又需要一大篇,所以在本文中,只把鎖定看作是一個臨時事件,這意味著如果一個資源被鎖定,它總會在以後某個時間被釋放。而死鎖發生在當多個程序訪問同一資料庫時,其中每個程序擁有的鎖都是其他程序所需的,由此造成每個程序都無法繼續下去。

  如何避免鎖

  我們可利用事務型資料庫中的隔離級別機制來避免鎖的建立,正確地使用隔離級別可使程式處理更多的併發事件(如允許多個使用者訪問資料),還能預防像丟失修改(Lost Update)、讀“髒”資料(Dirty Read)、不可重複讀(Nonrepeatable Read)及“虛”(Phantom)等問題。

  隔離級別 問題現象

  丟失修改 讀“髒”資料 不可重複讀 “虛”

  可重複讀取 No No No No

  讀取穩定性 No No No Yes

  游標穩定性 No No Yes Yes

  未提交的讀 No Yes Yes Yes

  表1:DB2的隔離級別與其對應的問題現象

  在只讀模式中,就可以防止鎖定發生,而不用那些未提交只讀隔離級別的含糊語句。一條SQL語句當使用了下列命令之一時,就應該考慮只讀模式了:

  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

  如果包含上述任一命令,可以說你的SQL語句有歧義性,因此,鎖可能就是造成其中資源問題的源頭。

  另外,以下是一些可降低鎖數目的建議:

  1、 將CURRENTDATA設為NO。這條命令告訴DB2模糊游標為只讀。

  2、 在適當的時候,儘可能使用User Uncommitted Read(使用者未提交的讀)。

  3、 儘可能關閉所有游標。

  4、 有一個正確的提交策略。確保程式不再使用資源時就立即釋放它。

  如何處理死鎖與超時

  在程式中使用重試邏輯,可處理以下三種SQL錯誤程式碼:

  1、 904:返回這個程式碼表示一條SQL語句是因為已達到資源限度而結束的。程式中可提交或回滾更改,並執行重試邏輯。

  2、 911:程式收到這個SQL程式碼,表示因為沒有為鎖列表分配足夠的記憶體,現在已達到資料庫的最大鎖數目。

  3、 912:程式收到這個SQL程式碼,表示死鎖或超時,依照904中的方法來解決。

  以下是一段Java程式碼,其捕捉返回的-911、-912、-904程式碼,並進行重試:

  for (int i = 0; i < MAX_RETRY_ATTEMPTS; i++) {
  //以下程式碼模擬一次事務
  try {
  stmt = conn.createStatement();
  System.out.println("Transaction started...");
  stmt.executeUpdate("UPDATE 1..."); //SQL語句1
  stmt.executeUpdate("UPDATE 2..."); // SQL語句2
  stmt.executeUpdate("UPDATE 3..."); // SQL語句3
  stmt.executeUpdate("UPDATE 3..."); // SQL語句4
  //提交所有更改
  conn.commit();
  System.out.println("事務已完成。");
  //確保只運行了一次。
  i = MAX_RETRY_ATTEMPTS;
  } catch (SQLException e) {
  /**
  *如果返回的SQL程式碼為-911,回滾會自動完成,程式回滾至前一次的提交狀態。
  *程式將進行重試。
  */
  if (-911 == e.getErrorCode()) {
  //等待RETRY_WAIT_TIME
  try {
  Thread.sleep(RETRY_WAIT_TIME);
  } catch (InterruptedException e1) {
  //即使休眠被打斷,但仍要重試。
  System.out.println("休眠被打斷。");
  }
  }
  /**
  *如果返回的SQL程式碼為-912,表示死鎖及超時。
  *如果是-904,代表已達到資源限度。
  *在這種情況下,程式將回滾並進行重試。
  */
  else if (-912 == e.getErrorCode() || -904 == e.getErrorCode()) {
  try {
  //需要回滾
  conn.rollback();
  } catch (SQLException e1) {
  System.out.println("無法回滾。"; color:black'> + e);
  }
  try {
  //等待RETRY_WAIT_TIME
  Thread.sleep(RETRY_WAIT_TIME);
  } catch (InterruptedException e1) {
  //即使休眠被打斷,但仍要重試。
  System.out.println("休眠被打斷。" + e1);
  }
  } else {
  //如果是其他錯誤,就不進行重試。
  i = MAX_RETRY_ATTEMPTS;
  System.out.println("有錯誤發生,錯誤程式碼:"
  + e.getErrorCode() + " SQL狀態:"
  + e.getSQLState() + "其他資訊:" + e.getMessage());
  }

  從上面也可看到,程式對死鎖、超時、最大鎖數目將會進行MAX_RETRY_ATTEMPTS次重試;其次,當“最大鎖數目”的情況發生時 (-911),程式不必手工進行回滾,因為此時的回滾是自動完成的;最後,無論何時返回-911、-904、-912程式碼,程式應在下次重試前等待 RETRY_WAIT_TIME一段時間。