SQL Server を使用しているときに、次のようなエラーが発生することがあります。
Transaction (Process ID 54) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
背景の詳細については、この問題についてJeff Atwood がブログで書いた内容を確認してください。
従来の JDBC を使用した小さなテストで、SQL Server のデッドロックをプログラムで作成したいと考えています。再試行ロジックをテストできるように、テストはすぐにデッドロックを作成する必要があります。
ジェフの分析を読んでわかったことは、いくつかのデータだけを持っていて、それをたくさん読んで、少し書くだけでよいということです。
私は現在、テーブルを作成し、いくつかのテストデータをテーブルに書き込む短い Java プログラム (以下) を持っています。それらのプログラムは、数百のスレッドを起動します。各スレッドは、テスト データの更新または読み取りを行います。更新操作と読み取り操作の比率を変更しましたが、比率に関係なく、プログラムでデッドロックを作成することはできません。このバージョンのテスト プログラムには、私の再試行ロジックがありません。SQL Server のデッドロックが確実に発生するようになったら、それを追加します。
すべてのスレッドを 1 つのプロセスで実行すると、何らかの方法で 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);
}
}
}