2012-09-06 25 views
1

有有時像這樣使用SQL Server時的錯誤:立即創建SQL Server死鎖的最小Java JDBC程序?

Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction. 

欲瞭解更多的背景,查了一下傑夫·阿特伍德有blogged這個問題。

我想通過使用普通JDBC的小型測試以編程方式創建SQL Server死鎖。測試應該立即創建一個死鎖,以便我可以測試一些重試邏輯。

我從傑夫的分析中得出的理解是,我只需要一些數據並閱讀很多內容,然後寫一點。

我目前有一個簡短的Java程序(如下),它創建一個表並將一些測試數據寫入表中。他們的程序啓動了幾百個線程。每個線程要麼進行更新,要麼讀取測試數據。我已經改變了更新與讀取操作的比率,但不管比率如何,我似乎無法以編程方式創建死鎖。這個版本的測試程序沒有我的重試邏輯,我會補充說,一旦我可以可靠地發生SQL Server死鎖。我想知道在單個進程中運行的所有線程是否可能以某種方式在JDBC驅動程序級別上序列化操作,因此我試着同時運行多個進程(在同一臺機器上),但仍然沒有死鎖。

import java.sql.*; 
import java.util.*; 
import java.util.concurrent.*; 
import static java.util.concurrent.TimeUnit.*; 

public class Deadlock { 
    static final int QUERY_THREAD_COUNT = 300, MAX_OPERATIONS_ITERATION = 5000; 
    static String server, db, user, pw; 
    static CountDownLatch latch = new CountDownLatch(QUERY_THREAD_COUNT); 

    public static void main(String... args) throws Exception { 
     server = args[0]; 
     db  = args[1]; 
     user = args[2]; 
     pw  = args[3]; 
     Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); 
     Connection connection = getConnection(); 
     Statement statement = connection.createStatement(); 
     statement.execute("CREATE TABLE TESTTABLE (BAR INTEGER, BAZ VARCHAR(32))"); 
     statement.execute("DELETE FROM TESTTABLE"); 
     statement.execute("INSERT INTO TESTTABLE VALUES (1, 'FOOBARBAZ')"); 
     connection.setAutoCommit(false); 
     connection.commit(); 
     connection.close(); 
     ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); 
     for (int i = 0; i < QUERY_THREAD_COUNT; ++i) { 
      scheduledExecutorService.scheduleWithFixedDelay(new Operation(), 0, 1, MILLISECONDS); 
     } 
     latch.await(); 
     System.exit(0); 
    } 

    static class Operation implements Runnable { 
     Connection connection = getConnection(); 
     Statement statement = getStatement(connection); 
     int iteration; 

     @Override 
     public void run() { 
      if (++iteration > MAX_OPERATIONS_ITERATION) { 
       latch.countDown(); 
       return; 
      } 
      try { 
       double random = Math.random(); 
       boolean update = (random < 0.01); 
       if (update) { 
        statement.executeUpdate("UPDATE TESTTABLE SET BAR=" + ((int) (random * 100)) + " WHERE BAZ='FOOBARBAZ'"); 
       } else { 
        ResultSet rs = statement.executeQuery("SELECT BAR, BAZ FROM TESTTABLE"); 
        if (! rs.next()) { 
         return; 
        } 
        int bar = rs.getInt(1); 
        String baz = rs.getString(2); 
        if (bar > 100) { 
         System.err.println("int is greater than 100"); 
        } 
        if (! baz.equals("FOOBARBAZ")) { 
         System.err.println("string is not FOOBARBAZ"); 
        } 
       } 
       connection.commit(); 
      } catch (SQLException sqle) { // <-- looking for a deadlock exception here! 
       System.err.println(sqle); 
      } 
     } 
    } 

    static Connection getConnection() { 
     try { 
      return DriverManager.getConnection("jdbc:sqlserver://" + server + ";databaseName=" + db + ";", user, pw); 
     } catch (Exception e) { 
      System.err.println(e); 
      throw new RuntimeException(e); 
     } 
    } 

    static Statement getStatement(Connection connection) { 
     try { 
      return connection.createStatement(); 
     } catch (Exception e) { 
      System.err.println(e); 
      throw new RuntimeException(e); 
     } 
    } 
} 
+0

http://dba.stackexchange.com/questions/309 –

回答

3

我覺得這個做的:

import java.sql.*; 
import java.util.*; 
import java.util.concurrent.*; 

/** 
* Creates an SQL Server deadlock. 
* 
* <pre> 
    javac SQLServerDeadlock.java && java -cp ".:sqljdbc.jar" SQLServerDeadlock <server> <db-name> <username> <password> 
* </pre> 
*/ 
public class SQLServerDeadlock { 
    static String server, db, user, pw; 
    static String TABLE_A = "TABLE_A", TABLE_B = "TABLE_B"; 
    static CountDownLatch latch = new CountDownLatch(2); 

    public static void main(String... args) throws SQLException { 
     server = args[0]; 
     db  = args[1]; 
     user = args[2]; 
     pw  = args[3]; 
     Connection connection = null; 
     try { 
      Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); 
      connection = getConnection(); 
      init(connection); 
      Thread t1 = new Thread(new Update(TABLE_A, TABLE_B), "A-THEN-B"); 
      Thread t2 = new Thread(new Update(TABLE_B, TABLE_A), "B-THEN-A"); 
      if (Math.random() < .5) { 
       t1.start(); 
       t2.start(); 
      } else { 
       t2.start(); 
       t1.start(); 
      } 
      t1.join(); 
      t2.join(); 
     } catch (Exception e) { 
      System.err.println(e); 
     } finally { 
      cleanup(connection); 
     } 
    } 

    static class Update implements Runnable { 
     String table1; 
     String table2; 

     Update(String table1, String table2) { 
      this.table1 = table1; 
      this.table2 = table2; 
     } 

     @Override 
     public void run() { 
      Connection connection = null; 
      try { 
       connection = getConnection(); 
       Statement statement = connection.createStatement(); 
       statement.executeUpdate("UPDATE " + table1 + " SET FOO=1"); 
       latch.countDown(); 
       latch.await(); 
       statement.executeUpdate("UPDATE " + table2 + " SET FOO=1"); 
       connection.commit(); 
       System.err.println(Thread.currentThread().getName() + ": SUCCESS!"); 
      } catch (SQLException sqle) { 
       if (sqle.getMessage().contains("Rerun the transaction")) { 
        System.err.println(Thread.currentThread().getName() + ": DEADLOCK VICTIM!"); 
       } 
       System.err.println(sqle); 
      } catch (InterruptedException ie) { 
       System.err.println(ie); 
      } finally { 
       try { 
        connection.close(); 
       } catch (SQLException sqle) { 
        System.err.println(sqle); 
       } 
      } 
     } 
    } 

    static void init(Connection connection) throws SQLException { 
     Statement statement = null; 
     try { 
      statement = connection.createStatement(); 
      for (String tableName : Arrays.asList(TABLE_A, TABLE_B)) { 
       if (tableExists(connection, tableName)) { 
        statement.execute("DROP TABLE " + tableName); 
       } 
       statement.execute("CREATE TABLE " + tableName + " (FOO INTEGER)"); 
       statement.execute("INSERT INTO " + tableName + " VALUES (0)"); 
      } 
      connection.commit(); 
     } finally { 
      statement.close(); 
     } 
    } 

    static void cleanup(Connection connection) throws SQLException { 
     if (connection == null) { 
      return; 
     } 
     Statement statement = null; 
     try { 
      statement = connection.createStatement(); 
      for (String tableName : Arrays.asList(TABLE_A, TABLE_B)) { 
       if (! tableExists(connection, tableName)) { 
        statement.execute("DROP TABLE " + tableName); 
       } 
      } 
      connection.commit(); 
     } finally { 
      statement.close(); 
     } 
    } 

    static boolean tableExists(Connection connection, String tableName) throws SQLException { 
     Statement statement = null; 
     try { 
      statement = connection.createStatement(); 
      String sql = 
       " SELECT TABLE_NAME       " + 
       " FROM INFORMATION_SCHEMA.TABLES   " + 
       " WHERE TABLE_CATALOG = '" + db  + "'" + 
       " AND TABLE_NAME = '" + tableName + "'"; 
      ResultSet rs = statement.executeQuery(sql); 
      return rs.next(); 
     } finally { 
      statement.close(); 
     } 
    } 

    static Connection getConnection() throws SQLException { 
     Connection connection = DriverManager.getConnection("jdbc:sqlserver://" + server + ";databaseName=" + db + ";", user, pw); 
     connection.setAutoCommit(false); 
     return connection; 
    } 
} 

線程的隨機啓動是沒有必要的,但不影響正確性。線程調度器應該任意交錯線程執行。然而,在我的環境中,我觀察到第二個線程幾乎但並不總是死鎖受害者。

+0

我跑了(修改後的版本 - 也許我弄壞了一些東西) - 但是我的SQLServer沒有死鎖(Express) - 我知道這是很久以前的 - 但是當你運行它時,你總是會給你一個僵局嗎? (我必須設置一些特定的URL標誌來實現這一點嗎?)乾杯 – monojohnny

+0

其實 - 我的錯!我已經從你的代碼中刪除了'setAutoCommit'!它現在陷入僵局!太好了!謝謝。 – monojohnny

+0

很高興爲你效勞 - 現在好運在應用程序中從死鎖中恢復過來! –

2

這裏是創建死鎖的僞代碼。

thread A: 
    conA.setAutoCommit(false); // use transactions 
    UPDATE TABLE_A SET AVALUE=5 
    sleep(5); // seconds 
    UPDATE TABLE_B SET BVALUE=5 
    conA.commit(); 

thread B: 
    conB.setAutoCommit(false); // use transactions 
    sleep(1); // let thread A go first 
    UPDATE TABLE_B SET BVALUE=5 
    UPDATE TABLE_A SET AVALUE=5 
    conB.commit(); 
+0

不要忘記'BEGIN TRANSACTION' –

+0

我在原來的文章中沒有提到這個,但我也有過類似的,或者甚至是相同的想法。我嘗試了兩個表格,並且我的更新任務都在兩次更新之間進行了一秒的固定延遲更改。由於幾個更新正在運行,我認爲可能會導致死鎖情況。我沒有看到一個,但我會再次嘗試這種方法。也許有些同步對象像一個鎖存器來協調兩個更新線程... –

+0

哦,等待......我只關閉第一個連接上的自動提交... –