Фантомное чтение (phantom reads). В результатах повторяющегося запроса появляются и исчезают строки, которые в данный момент модифицирует параллельная транзакция. ^ebb2ec
Эта проблема присутствует на уровне изоляции Repeatable read, Read committed и Read uncommitted.
Рассмотрим пример. Открываем первую транзакцию. Запрашиваем количество строк в таблице пользователей, получаем ответ 2. Далее стартуем вторую транзакцию, а поток с первой транзакцией засыпает. Вторая транзакция добавляет новую запись в таблицу пользователей и коммитит изменения. После этого первая транзакция продолжается. Снова запрашиваем количество строк, получаем на этот раз ответ 3.
public class PhantomRead {
private static final int ISOLATION_LEVEL = Connection.TRANSACTION_READ_COMMITTED;
public static void main(String[] args) {
try(
final Connection connection = Repository.getConnection();
final Statement statement = connection.createStatement()
) {
connection.setAutoCommit(false);
connection.setTransactionIsolation(ISOLATION_LEVEL);
final ResultSet resultSet = statement.executeQuery("SELECT count(*) FROM person");
while (resultSet.next()) {
final int count = resultSet.getInt(1);
System.out.println("Count: " + count);
}
new OtherTransaction().start();
Thread.sleep(2000);
final ResultSet resultSetTwo = statement.executeQuery("SELECT count(*) FROM person");
while (resultSetTwo.next()) {
final int count = resultSetTwo.getInt(1);
System.out.println("Count: " + count);
}
} catch (SQLException | InterruptedException e) {
throw new RuntimeException(e);
}
}
static class OtherTransaction extends Thread {
@Override
public void run() {
try (
final Connection connection = Repository.getConnection();
final Statement statement = connection.createStatement()
) {
connection.setAutoCommit(false);
connection.setTransactionIsolation(ISOLATION_LEVEL);
statement.executeUpdate("INSERT INTO person(id, balance) values (3, 1000)");
connection.commit();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
}
}
Чтобы устранить эту проблему, используем самый изолированный уровень Serializable. Однако, для PostgreSQL будет достаточно и REPEATABLE_READ
.
Но мы всё равно установим SERIALIZABLE
. Теперь в первой транзакции нам дважды возвращается ответ 2, несмотря на то, что в таблице появились новые записи.
Дополнительные материалы