【问题标题】:Does H2 support the serializable isolation level?H2 是否支持可序列化的隔离级别?
【发布时间】: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_isolationPlease 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


【解决方案1】:

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

随后的insertOne(connection1) 需要一个范围锁,它与来自外部事务 2 的共享锁不兼容。因此连接 1 将进入“等待”(轮询)状态。如果不使用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        |               |

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-16
    • 2019-11-14
    • 2021-07-25
    • 2021-01-14
    • 2023-03-04
    • 2017-10-20
    • 2018-10-23
    相关资源
    最近更新 更多