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

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


person Andrey Agibalov    schedule 25.01.2015    source источник
comment
Что произойдет, если вы используете базу данных mysql вместо InnoDB в качестве механизма хранения?   -  person MinecraftShamrock    schedule 25.01.2015
comment
Это не фантомное чтение. Фантомное чтение происходит, когда два чтения в одной и той же транзакции возвращают разные результаты вследствие вставки, выполненной другой транзакцией. Код, который вы разместили, должен вести себя так, как вы ожидаете... возможно, что-то не так с вашей конфигурацией БД.   -  person agori    schedule 25.01.2015
comment
Это ЯВЛЯЕТСЯ фантомным чтением. Есть 2 идентичных запроса, которые я запускаю до и после вставки, и они дают разные результаты. Wiki говорит: когда в ходе транзакции выполняются два одинаковых запроса, и набор строк, возвращаемый вторым запросом, отличается от первого.   -  person Andrey Agibalov    schedule 25.01.2015
comment
@agori Тогда, пожалуйста, напишите ответ и объясните, почему это законно и как это предотвратить. Это было бы очень полезно для нас :)   -  person MinecraftShamrock    schedule 26.01.2015
comment
Я протестировал его с таблицей MySQL InnoDB, и это сработало так, как вы ожидаете, что ваш пример будет работать. Но на самом деле вопрос заключается в том, соответствует ли это поведение ACID и почему. Таким образом, либо 1) не имеет значения, было ли совершено действие, вызывающее фантомное чтение, чтобы быть законным фантомным чтением, ИЛИ 2) вся ваша база данных настроена так, чтобы не поддерживать транзакции каким-либо образом   -  person MinecraftShamrock    schedule 26.01.2015
comment
@loki2302 Мне кажется, транзакции не работают. Как будто вы каким-то образом используете режим автоматической фиксации. Фантомное чтение требует, чтобы вторая транзакция отправила оператор фиксации в базу данных перед вторым чтением первой транзакции.   -  person agori    schedule 26.01.2015


Ответы (1)


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

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

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

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

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

person fredt    schedule 31.01.2015