Gre*_*tes 2 java sql-server deadlock jdbc
使用 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.
Run Code Online (Sandbox Code Playgroud)
有关更多背景信息,请查看 Jeff Atwood撰写的有关此问题的博客文章。
我想通过使用普通 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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我认为这样做:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
线程开始的随机化不是必需的,但不影响正确性。线程调度器应该任意交错线程执行。但是,在我的环境中,我观察到启动的第二个线程几乎(但并非总是)成为死锁的受害者。
| 归档时间: |
|
| 查看次数: |
3994 次 |
| 最近记录: |