H2是否支持可序列化的隔离级别?

维基百科将Phantom读取现象描述为:

当在事务过程中执行两个相同的查询,并且第二个查询返回的行集合与第一个查询不同时,会发生幻像读取。

它还指出,对于可序列化的隔离级别,Phantom读取是不可能的。 我试图确保它在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所有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。

在启用隔离级别“可序列化”时存在悲观锁定,分别在连接1和2上的前两个读操作应该导致两个共享(写)锁。

后续的insertOne(connection1)需要一个范围锁与来自外来事务2的共享锁不兼容。因此,连接1将进入“等待”(轮询)状态。 不使用setQueryTimeout(1)您的应用程序将挂起。

对于https://en.wikipedia.org/wiki/Isolation_(database_systems)#Phantom_reads,您应该通过手动启动两个JVM实例或使用不同的线程来更改您的应用程序(不使用setQueryTimeout )以允许以下计划:

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

如果不支持“可序列化”,您将看到:

 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