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

84 lines
4.1 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:
- dirty reads
tags:
- зрелость/🌱
date:
- - 2024-06-19
zero-link:
- "[[00 Базы Данных]]"
parents:
- "[[Проблемы при параллельном выполнении нескольких транзакций]]"
linked:
link: 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](Read%20uncommitted.md).
Рассмотрим на примере. У нас будет два параллельных потока. В первом мы открываем транзакцию и устанавливаем новый баланс первому пользователю равным 100_000. Но транзакцию не коммитим. Вместо этого, запускаем вторую параллельную транзакцию в отдельном потоке, а текущий поток засыпает на 2 секунды.
Во второй транзакции считывается баланс пользователя из БД и выводится в консоль. Значение баланса будет 100_000, несмотря на то, что первая транзакция ещё не закоммитила свои изменения.
Но дальше самое интересное, мы выполняем rollback первой транзакции, и баланс пользователя снова становится 1000. То есть вторая транзакция работала с не валидными данными.
```java
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());
}
}
}
}
```
Результат работы примера:
```log
Balance: 100000
```
Для устранения этого эффекта устанавливаем уровень изоляции в [Read committed](Read%20committed.md). Теперь при выполнении второй транзакции получаем баланс 1000, вместо 100_000.
Результат работы примера с `READ_COMMITTED`:
```log
Balance: 1000
```
## Дополнительные материалы
- [Пример реализации на Java](https://github.com/Example-uPagge/transactional/blob/master/jdbc-transaction/src/main/java/dev/struchkov/example/transaction/problems/DirtyReadExample.java)