84 lines
4.1 KiB
Markdown
84 lines
4.1 KiB
Markdown
---
|
||
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) |