80 lines
4.2 KiB
Markdown
80 lines
4.2 KiB
Markdown
---
|
||
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) |