digital-garden/_inbox/Фантомное чтение.md

4.2 KiB
Raw Blame History

aliases tags date zero-link parents linked link
phantom reads
зрелость/🌱
2024-06-19
00 Базы Данных
Проблемы при параллельном выполнении нескольких транзакций
https://struchkov.dev/blog/ru/transactional-isolation-levels/#%D1%84%D0%B0%D0%BD%D1%82%D0%BE%D0%BC%D0%BD%D0%BE%D0%B5-%D1%87%D1%82%D0%B5%D0%BD%D0%B8%D0%B5

Фантомное чтение (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, несмотря на то, что в таблице появились новые записи.

Дополнительные материалы