gpt4 book ai didi

java - 立即创建 SQL Server 死锁的最小 Java JDBC 程序?

转载 作者:行者123 更新时间:2023-11-30 05:51:53 25 4
gpt4 key购买 nike

使用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 的 blogged关于这个问题。

我想通过使用普通旧式 JDBC 的小测试以编程方式创建 SQL Server 死锁。测试应该立即创建一个死锁,以便我可以测试一些重试逻辑。

根据阅读 Jeff 的分析,我的理解是我只需要有一些数据并大量阅读,然后写一点。

我目前有一个简短的 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);
}
}
}

最佳答案

我认为这样做:

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;
}
}

线程启动的随机化不是必需的,但不会影响正确性。线程调度程序应该任意交错执行线程。但是,在我的环境中,我观察到第二个启动的线程几乎(但并非总是)成为死锁受害者。

关于java - 立即创建 SQL Server 死锁的最小 Java JDBC 程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12308234/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com