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

时间:2015-01-26 06:43:34

标签: java jdbc transactions h2 isolation-level

维基百科将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个查询返回相同的行集(在我的情况下为空集)。

但是这不会发生:最后一个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 个答案:

答案 0 :(得分:1)

在启用隔离级别时存在悲观锁定"可序列化"对连接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