Почему одновременные соединения с БД видят незавершенные изменения друг друга, хотя изоляция установлена ​​на "чтение зафиксировано"?

Я пытаюсь провести несколько тестов, чтобы понять, как можно использовать уровни изоляции транзакций для решения различных проблем параллелизма. Я начал с 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 ответ

Решение

Ваша внутренняя настройка базы данных не подходит для теста, который вы выполняете.

Альтернативы:

  1. Попробуйте запустить сервер и повторите свои тесты с MVCC.
  2. Попробуйте использовать отдельный поток для каждого соединения с MVCC и внутрипроцессной базой данных.

При использовании в процессе, как в вашем примере, HSQLDB требует, чтобы каждое соединение принадлежало отдельному потоку. В противном случае функции параллелизма Java не будут работать.

При работе с отдельными потоками вам нужно использовать модель транзакций MVCC, чтобы это работало. В используемом по умолчанию режиме блокировки по умолчанию вставка заблокирует другое соединение до фиксации.

Другие вопросы по тегам