为什么并发数据库连接会看到彼此未提交的更改,尽管隔离设置为" read committed"?

时间:2015-01-25 15:44:24

标签: java transactions hsqldb isolation-level transaction-isolation

我正在尝试进行一些测试,以了解如何使用事务隔离级别来解决各种并发问题。我从TRANSACTION_READ_COMMITED开始,但最简单的情况并不像我期望的那样。这是代码:

try(Connection connection1 = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
    connection1.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
    connection1.setAutoCommit(false);

    try(Connection connection2 = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
        connection2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        connection2.setAutoCommit(false);

        assertEquals(0, selectAll(connection1));
        assertEquals(0, selectAll(connection2));

        insertOne(connection1);
        assertEquals(0, selectAll(connection2)); // there is 1 row!
    }
}

在这里,我设置了2个并发连接,在它们两个中启动事务,在第一个连接中进行更改,并期望在第二个连接中看不到它们。这不起作用:连接1中未提交的更改对连接2可见。

我使用在嵌入式模式下运行的HSQLDB 2.3.2和内存数据库。以下是我的selectAll / insert辅助方法的实现:

private static void initSchema() 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/aad49a5a2c26d5fda2b3

此代码有问题,或者HSQLDB的行为方式不正确吗?

更新:重新阅读维基后,我相信我的想法是错误的。我在这里看到的是“幻读”。 READ_COMMITTED并不保证幻像读取永远不会发生。我应该检查的是,用一行预先推送表格,通过connection1更新它,并确保通过connection2看不到此更改,除非提交更改。此外,一般并不能保证此更改在提交后立即可见:可能变得可见,但无法保证。

1 个答案:

答案 0 :(得分:1)

您的进程内数据库设置不适合您正在执行的测试。

替代方案是:

  1. 尝试运行服务器并使用MVCC重复测试。
  2. 尝试为MVCC和进程内数据库的每个连接使用单独的线程。
  3. 在进程中使用时,就像您的示例一样,HSQLDB要求每个连接都由一个单独的线程拥有。否则Java并发功能将无效。

    使用单独的线程运行时,您需要使用MVCC事务模型才能工作。在您使用的默认锁定模式下,插入将锁定另一个连接,直到提交。