--- aliases: - non-repeatable reads tags: - зрелость/🌱 date: - - 2024-06-19 zero-link: - "[[00 Базы Данных]]" parents: - "[[Проблемы при параллельном выполнении нескольких транзакций]]" linked: link: https://struchkov.dev/blog/ru/transactional-isolation-levels/#%D0%BD%D0%B5%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D1%8F%D1%8E%D1%89%D0%B5%D0%B5%D1%81%D1%8F-%D1%87%D1%82%D0%B5%D0%BD%D0%B8%D0%B5 --- **Неповторяющееся чтение (non-repeatable reads).** Запрос с одними и теми же условиями даёт неодинаковые результаты в рамках транзакции. ^3e8781 Эта проблема присутствует на уровне изоляции [Read committed](Read%20committed.md) и [Read uncommitted](Read%20uncommitted.md). Рассмотрим пример. Начинаем первую транзакцию. Считываем баланс пользователя, получаем значение 1000. Далее стартует вторая транзакция в отдельном потоке, а текущий поток засыпает. Во второй транзакции устанавливаем пользователю баланс равный 100_000. После чего закрываем вторую транзакцию. Баланс успешно обновился в БД. Первая транзакция продолжает выполнение. Снова запрашивает баланс пользователя из БД, на этот раз получает значение 100_000. Таким образом, вторая транзакция повлияла на выполнение первой. ```java public class NonRepeatableRead { 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 resultSetOne = statement.executeQuery("SELECT * FROM person WHERE id = 1"); while (resultSetOne.next()) { final String balance = resultSetOne.getString("balance"); System.out.println("[one] Balance: " + balance); } new OtherTransaction().start(); Thread.sleep(2000); final ResultSet resultSetTwo = statement.executeQuery("SELECT * FROM person WHERE id = 1"); while (resultSetTwo.next()) { final String balance = resultSetTwo.getString("balance"); System.out.println("[one] Balance: " + balance); } } 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("UPDATE person SET balance = 100000 WHERE id = 1"); connection.commit(); final ResultSet resultSetTwo = statement.executeQuery("SELECT * FROM person WHERE id = 1"); while (resultSetTwo.next()) { final String balance = resultSetTwo.getString("balance"); System.out.println("[two] Balance: " + balance); } connection.commit(); } catch (SQLException e) { System.out.println(e.getMessage()); } } } } ``` Результат работы примера: ```log [one] Balance: 1000 [two] Balance: 100000 [one] Balance: 100000 ``` Для устранения этой проблемы воспользуемся известным нам уровнем изоляции [Repeatable read](Repeatable%20read.md). Тогда при выполнении примера первая транзакция дважды получит значение баланса равным 1000, несмотря на то, что в БД будет уже 100_000. Результат работы примера с `REPEATABLE_READ`: ```log [one] Balance: 1000 [two] Balance: 100000 [one] Balance: 1000 ``` ## Дополнительные материалы - [Пример на Java](https://github.com/Example-uPagge/transactional/blob/master/jdbc-transaction/src/main/java/dev/struchkov/example/transaction/problems/NonRepeatableRead.java)