H2 是否支持可序列化隔离级别?
Does H2 support the serializable isolation level?
维基百科将幻读现象描述为:
A phantom read occurs when, in the course of a transaction, two identical queries are executed, and the collection of rows returned by the second query is different from the first.
它还指出,使用可序列化隔离级别,幻读是不可能的。我试图确保它在 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
查询到 return 同一组行(在我的例子中是一个空集)。
但这并没有发生:最后的 selectAll(connection2)
return 是刚刚插入并行连接的行。 我错了吗,这种行为是预料之中的,还是 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.
启用隔离级别时存在悲观锁定"serializable"您对连接 1 和 2 的前两个读取操作分别应导致两个共享(写入)锁。
随后的insertOne(connection1)
需要一个范围锁,它与外来事务2的共享锁不兼容。因此连接1将进入"wait"(轮询)状态。如果不使用 setQueryTimeout(1)
,您的应用程序将挂起。
关于 https://en.wikipedia.org/wiki/Isolation_(database_systems)#Phantom_reads 您应该更改您的应用程序(不使用 setQueryTimeout
)以允许以下计划,通过手动启动两个 JVM 实例或使用不同的线程:
Transaction 1 | Transaction 2 | Comment
--------------+---------------+--------
- | selectAll | Acquiring shared lock in T2
insert | - | Unable to acquire range lock
wait | - | T1 polling
wait | selectAll | T2 gets identical row set
wait | - |
wait | commit | T2 releasing shared lock
| | T1 resuming insert
commit | |
如果 "serializable" 不受支持,您将看到:
Transaction 1 | Transaction 2 | Comment
--------------+---------------+--------
- | selectAll | Acquiring shared lock in T2
insert | - | No need for range lock due to missing support
commit | | T1 releasing all locks
| selectAll | T2 gets different row set
作为办公室文档 set_lock_mode ,To enable, execute the SQL statement SET LOCK_MODE 1 or append ;LOCK_MODE=1 to the database URL: jdbc:h2:~/test;LOCK_MODE=1
维基百科将幻读现象描述为:
A phantom read occurs when, in the course of a transaction, two identical queries are executed, and the collection of rows returned by the second query is different from the first.
它还指出,使用可序列化隔离级别,幻读是不可能的。我试图确保它在 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
查询到 return 同一组行(在我的例子中是一个空集)。
但这并没有发生:最后的 selectAll(connection2)
return 是刚刚插入并行连接的行。 我错了吗,这种行为是预料之中的,还是 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.
启用隔离级别时存在悲观锁定"serializable"您对连接 1 和 2 的前两个读取操作分别应导致两个共享(写入)锁。
随后的insertOne(connection1)
需要一个范围锁,它与外来事务2的共享锁不兼容。因此连接1将进入"wait"(轮询)状态。如果不使用 setQueryTimeout(1)
,您的应用程序将挂起。
关于 https://en.wikipedia.org/wiki/Isolation_(database_systems)#Phantom_reads 您应该更改您的应用程序(不使用 setQueryTimeout
)以允许以下计划,通过手动启动两个 JVM 实例或使用不同的线程:
Transaction 1 | Transaction 2 | Comment
--------------+---------------+--------
- | selectAll | Acquiring shared lock in T2
insert | - | Unable to acquire range lock
wait | - | T1 polling
wait | selectAll | T2 gets identical row set
wait | - |
wait | commit | T2 releasing shared lock
| | T1 resuming insert
commit | |
如果 "serializable" 不受支持,您将看到:
Transaction 1 | Transaction 2 | Comment
--------------+---------------+--------
- | selectAll | Acquiring shared lock in T2
insert | - | No need for range lock due to missing support
commit | | T1 releasing all locks
| selectAll | T2 gets different row set
作为办公室文档 set_lock_mode ,To enable, execute the SQL statement SET LOCK_MODE 1 or append ;LOCK_MODE=1 to the database URL: jdbc:h2:~/test;LOCK_MODE=1