digital-garden/_inbox/Фантомное чтение.md

80 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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:
- phantom reads
tags:
- зрелость/🌱
date:
- - 2024-06-19
zero-link:
- "[[00 Базы Данных]]"
parents:
- "[[Проблемы при параллельном выполнении нескольких транзакций]]"
linked:
link: https://struchkov.dev/blog/ru/transactional-isolation-levels/#%D1%84%D0%B0%D0%BD%D1%82%D0%BE%D0%BC%D0%BD%D0%BE%D0%B5-%D1%87%D1%82%D0%B5%D0%BD%D0%B8%D0%B5
---
**Фантомное чтение (phantom reads).** В результатах повторяющегося запроса появляются и исчезают строки, которые в данный момент модифицирует параллельная транзакция. ^ebb2ec
Эта проблема присутствует на уровне изоляции [Repeatable read](Repeatable%20read.md), [Read committed](Read%20committed.md) и [Read uncommitted](Read%20uncommitted.md).
Рассмотрим пример. Открываем первую транзакцию. Запрашиваем количество строк в таблице пользователей, получаем ответ 2. Далее стартуем вторую транзакцию, а поток с первой транзакцией засыпает. Вторая транзакция добавляет новую запись в таблицу пользователей и коммитит изменения. После этого первая транзакция продолжается. Снова запрашиваем количество строк, получаем на этот раз ответ 3.
```java
public class PhantomRead {
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 resultSet = statement.executeQuery("SELECT count(*) FROM person");
while (resultSet.next()) {
final int count = resultSet.getInt(1);
System.out.println("Count: " + count);
}
new OtherTransaction().start();
Thread.sleep(2000);
final ResultSet resultSetTwo = statement.executeQuery("SELECT count(*) FROM person");
while (resultSetTwo.next()) {
final int count = resultSetTwo.getInt(1);
System.out.println("Count: " + count);
}
} 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("INSERT INTO person(id, balance) values (3, 1000)");
connection.commit();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
}
}
```
Чтобы устранить эту проблему, используем самый изолированный уровень [Serializable](Serializable.md). Однако, для PostgreSQL будет достаточно и `REPEATABLE_READ`.
Но мы всё равно установим `SERIALIZABLE`. Теперь в первой транзакции нам дважды возвращается ответ 2, несмотря на то, что в таблице появились новые записи.
## Дополнительные материалы
- [Пример на Java](https://github.com/Example-uPagge/transactional/blob/master/jdbc-transaction/src/main/java/dev/struchkov/example/transaction/problems/PhantomRead.java)