digital-garden/_inbox/Неповторяющееся чтение.md

106 lines
4.9 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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)