为什么并发数据库连接看到彼此未提交的更改,尽管隔离设置为 "read committed"?
Why do the concurrent DB connections see each others uncommitted changes though isolation is set to "read committed"?
我正在尝试进行一些测试以了解如何使用事务隔离级别来解决各种并发问题。我从 TRANSACTION_READ_COMMITED
开始,但最简单的情况并不符合我的预期。这是代码:
try(Connection connection1 = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
connection1.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection1.setAutoCommit(false);
try(Connection connection2 = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
connection2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection2.setAutoCommit(false);
assertEquals(0, selectAll(connection1));
assertEquals(0, selectAll(connection2));
insertOne(connection1);
assertEquals(0, selectAll(connection2)); // there is 1 row!
}
}
在这里,我设置了 2 个并发连接,在两个连接中启动事务,在第一个连接中进行更改,并希望在第二个连接中看不到它们。这不起作用:连接 1 中未提交的更改对连接 2 可见。
我在内存数据库的嵌入式模式下使用 HSQLDB 2.3.2 运行。以下是我的 selectAll/insert 辅助方法的实现:
private static void initSchema() throws SQLException {
try(Connection connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
try (PreparedStatement s = connection.prepareStatement(
"create table Notes(text varchar(256) not null)")) {
s.executeUpdate();
}
}
}
private static int selectAll(Connection connection) throws SQLException {
int count = 0;
try (PreparedStatement s = connection.prepareStatement("select * from Notes")) {
s.setQueryTimeout(1);
try (ResultSet resultSet = s.executeQuery()) {
while (resultSet.next()) {
++count;
}
}
}
return count;
}
private static void insertOne(Connection connection) throws SQLException {
try(PreparedStatement s = connection.prepareStatement("insert into Notes(text) values(?)")) {
s.setString(1, "hello");
s.setQueryTimeout(1);
s.executeUpdate();
}
}
完整的测试可以在这里找到:https://gist.github.com/loki2302/aad49a5a2c26d5fda2b3
这段代码有问题吗,或者 HSQLDB 没有按应有的方式运行?
更新: 重新阅读wiki后,我认为我这里的想法是错误的。我在这里看到的是一个"phantom read"。 READ_COMMITTED
不保证永远不会发生幻读。相反,我应该检查的是,用单行预传播 table,通过 connection1
更新它,并确保通过 connection2
不可见此更改,除非更改已提交。此外,通常不能保证此更改在提交后立即变得可见:它可能变得可见,但不能保证。
您的进程内数据库设置不适合您正在执行的测试。
备选方案是:
- 尝试 运行连接服务器并使用 MVCC 重复测试。
- 尝试为每个与 MVCC 和进程内数据库的连接使用单独的线程。
当在进程内使用时,就像您的示例一样,HSQLDB 要求每个连接都由一个单独的线程拥有。否则 Java 并发功能将无法工作。
当 运行 使用单独的线程时,您需要使用 MVCC 事务模型才能工作。在您使用的默认锁定模式下,插入将锁定另一个连接,直到提交。
我正在尝试进行一些测试以了解如何使用事务隔离级别来解决各种并发问题。我从 TRANSACTION_READ_COMMITED
开始,但最简单的情况并不符合我的预期。这是代码:
try(Connection connection1 = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
connection1.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection1.setAutoCommit(false);
try(Connection connection2 = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
connection2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection2.setAutoCommit(false);
assertEquals(0, selectAll(connection1));
assertEquals(0, selectAll(connection2));
insertOne(connection1);
assertEquals(0, selectAll(connection2)); // there is 1 row!
}
}
在这里,我设置了 2 个并发连接,在两个连接中启动事务,在第一个连接中进行更改,并希望在第二个连接中看不到它们。这不起作用:连接 1 中未提交的更改对连接 2 可见。
我在内存数据库的嵌入式模式下使用 HSQLDB 2.3.2 运行。以下是我的 selectAll/insert 辅助方法的实现:
private static void initSchema() throws SQLException {
try(Connection connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
try (PreparedStatement s = connection.prepareStatement(
"create table Notes(text varchar(256) not null)")) {
s.executeUpdate();
}
}
}
private static int selectAll(Connection connection) throws SQLException {
int count = 0;
try (PreparedStatement s = connection.prepareStatement("select * from Notes")) {
s.setQueryTimeout(1);
try (ResultSet resultSet = s.executeQuery()) {
while (resultSet.next()) {
++count;
}
}
}
return count;
}
private static void insertOne(Connection connection) throws SQLException {
try(PreparedStatement s = connection.prepareStatement("insert into Notes(text) values(?)")) {
s.setString(1, "hello");
s.setQueryTimeout(1);
s.executeUpdate();
}
}
完整的测试可以在这里找到:https://gist.github.com/loki2302/aad49a5a2c26d5fda2b3
这段代码有问题吗,或者 HSQLDB 没有按应有的方式运行?
更新: 重新阅读wiki后,我认为我这里的想法是错误的。我在这里看到的是一个"phantom read"。 READ_COMMITTED
不保证永远不会发生幻读。相反,我应该检查的是,用单行预传播 table,通过 connection1
更新它,并确保通过 connection2
不可见此更改,除非更改已提交。此外,通常不能保证此更改在提交后立即变得可见:它可能变得可见,但不能保证。
您的进程内数据库设置不适合您正在执行的测试。
备选方案是:
- 尝试 运行连接服务器并使用 MVCC 重复测试。
- 尝试为每个与 MVCC 和进程内数据库的连接使用单独的线程。
当在进程内使用时,就像您的示例一样,HSQLDB 要求每个连接都由一个单独的线程拥有。否则 Java 并发功能将无法工作。
当 运行 使用单独的线程时,您需要使用 MVCC 事务模型才能工作。在您使用的默认锁定模式下,插入将锁定另一个连接,直到提交。