【发布时间】:2015-03-24 14:32:24
【问题描述】:
维基百科将幻读现象描述为:
当在事务过程中执行两个相同的查询,并且第二个查询返回的行集合与第一个不同时,就会发生幻读。
它还指出,在可序列化的隔离级别下,幻读是不可能的。我试图确保在 H2 中也是如此,但要么我期待错误的事情,要么我做错了事情,或者 H2 有问题。不过,这里是代码:
try(Connection connection1 = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
connection1.setAutoCommit(false);
try(Connection connection2 = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
connection2.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
connection2.setAutoCommit(false);
assertEquals(0, selectAll(connection1));
assertEquals(0, selectAll(connection2)); // A: select
insertOne(connection1); // B: insert
assertEquals(1, selectAll(connection1));
assertEquals(0, selectAll(connection2)); // A: select
connection1.commit(); // B: commit for insert
assertEquals(1, selectAll(connection1));
assertEquals(0, selectAll(connection2)); // A: select ???
}
}
在这里,我启动了 2 个并发连接并将其中一个配置为具有可序列化的事务隔离。之后,我确保两者都看不到任何数据。然后,使用connection1,我插入一个新行。之后,我确保connection1 可以看到这个新行,但connection2 看不到。然后,我提交更改并希望connection2 继续不知道此更改。简而言之,我希望我的所有 A: select 查询都返回相同的行集(在我的例子中是一个空集)。
但这不会发生:最后一个selectAll(connection2) 返回刚刚插入并行连接的行。 我错了,这种行为是预期的,还是 H2 有问题?
这里是辅助方法:
public void setUpDatabase() 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/26f3c052f7e73fd22604
我使用 H2 1.4.185。
【问题讨论】:
-
FWIW,在源代码 (code.google.com/p/h2database/source/browse/trunk/h2/src/main/…) 中,SERIALIZABLE 与 REPEATABLE_READ 执行相同的操作。所以可能不支持。
-
OTOH。文档 (h2database.com/html/advanced.html#transaction_isolation) 声明支持 SERIALIZABLE:当使用隔离级别“serializable”时,禁止脏读、不可重复读和幻读。
-
为两个连接设置事务隔离级别有帮助吗?还是在您开始两者中的任何一个之前进行第三个连接?这不应该是一个全局设置吗?
-
SERIALIZABLE包括REPEATABLE_READ,所以如果H2 将REPEATABLE_READ处理为SERIALIZABLE就足够公平了。将两个连接都设置为SERIALIZABLE并没有帮助。这确实不应该是一个全局设置:对于某些操作,应用程序可能对默认的READ_COMMITTED感到满意,但对于其他操作,它可能需要SERIALIZABLE。 -
来自h2database.com/html/advanced.html#transaction_isolation
Please note MVCC is enabled in version 1.4.x by default, when using the MVStore. In this case, table level locking is not used. Instead, rows are locked for update, and *read committed* is used in all cases (changing the isolation level has no effect)。看起来在 MVCC 中默认使用已提交的读取。
标签: java jdbc transactions h2 isolation-level