digital-garden/_inbox/Грязное чтение.md

4.1 KiB
Raw Blame History

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

«Грязное» чтение (dirty reads) — в результатах запроса появляются промежуточные результаты параллельной транзакции, которая ещё не завершилась. ^c744ef

Эта проблема наблюдается при уровне изоляции Read uncommitted.

Рассмотрим на примере. У нас будет два параллельных потока. В первом мы открываем транзакцию и устанавливаем новый баланс первому пользователю равным 100_000. Но транзакцию не коммитим. Вместо этого, запускаем вторую параллельную транзакцию в отдельном потоке, а текущий поток засыпает на 2 секунды.

Во второй транзакции считывается баланс пользователя из БД и выводится в консоль. Значение баланса будет 100_000, несмотря на то, что первая транзакция ещё не закоммитила свои изменения.

Но дальше самое интересное, мы выполняем rollback первой транзакции, и баланс пользователя снова становится 1000. То есть вторая транзакция работала с не валидными данными.

public class DirtyReadExample {

    private static final int ISOLATION_LEVEL = Connection.TRANSACTION_READ_UNCOMMITTED;

    public static void main(String[] args) throws SQLException, InterruptedException {
        try (
                final Connection connection = Repository.getConnectionH2();
                final Statement statement = connection.createStatement()
        ) {
            connection.setAutoCommit(false);
            connection.setTransactionIsolation(ISOLATION_LEVEL);

            statement.executeUpdate("UPDATE person SET balance = 100000 WHERE id = 1");

            new OtherTransaction().start();
            Thread.sleep(2000);
            connection.rollback();
        }

    }

    static class OtherTransaction extends Thread {
        @Override
        public void run() {
            try (
                    final Connection connection = Repository.getConnectionH2();
                    final Statement statement = connection.createStatement()
            ) {
                connection.setAutoCommit(false);
                connection.setTransactionIsolation(ISOLATION_LEVEL);

                final ResultSet resultSet = statement.executeQuery("SELECT * FROM person WHERE id = 1");
                while (resultSet.next()) {
                    System.out.println("Balance: " + resultSet.getString("balance"));
                }
            } catch (SQLException e) {
                System.out.println(e.getMessage());
            }
        }
    }

}

Результат работы примера:

Balance: 100000

Для устранения этого эффекта устанавливаем уровень изоляции в Read committed. Теперь при выполнении второй транзакции получаем баланс 1000, вместо 100_000.

Результат работы примера с READ_COMMITTED:

Balance: 1000

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