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

106 lines
4.9 KiB
Markdown
Raw Permalink Normal View History

2024-06-19 20:03:52 +03:00
---
aliases:
- non-repeatable reads
tags:
- зрелость/🌱
date:
- - 2024-06-19
zero-link:
- "[[00 Базы Данных]]"
parents:
- "[[Проблемы при параллельном выполнении нескольких транзакций]]"
2024-06-19 20:13:52 +03:00
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
2024-06-19 20:03:52 +03:00
---
2024-06-19 20:13:52 +03:00
**Неповторяющееся чтение (non-repeatable reads).** Запрос с одними и теми же условиями даёт неодинаковые результаты в рамках транзакции. ^3e8781
Эта проблема присутствует на уровне изоляции [Read committed](Read%20committed.md) и [Read uncommitted](Read%20uncommitted.md).
Рассмотрим пример. Начинаем первую транзакцию. Считываем баланс пользователя, получаем значение 1000. Далее стартует вторая транзакция в отдельном потоке, а текущий поток засыпает.
Во второй транзакции устанавливаем пользователю баланс равный 100_000. После чего закрываем вторую транзакцию. Баланс успешно обновился в БД.
2024-06-19 20:18:52 +03:00
Первая транзакция продолжает выполнение. Снова запрашивает баланс пользователя из БД, на этот раз получает значение 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)