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