На каком уровне изоляции отсутствует фантомное чтение в PostgreSQL
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Фантомное чтение в PostgreSQL и уровни изоляции
Этот вопрос требует глубокого понимания транзакций и уровней изоляции. В PostgreSQL фантомное чтение отсутствует на уровне SERIALIZABLE. На уровне REPEATABLE READ (которая в PostgreSQL реализует Snapshot Isolation) фантомное чтение ТАКЖЕ отсутствует.
Что такое фантомное чтение?
Фантомное чтение (Phantom Read) — это ситуация, когда одна транзакция дважды выполняет один и тот же запрос SELECT и получает разное количество строк.
// Пример фантомного чтения:
// Транзакция 1:
SELECT COUNT(*) FROM orders WHERE status = 'pending'; -- Результат: 5
// Между тем Транзакция 2 добавляет новый заказ:
INSERT INTO orders (status) VALUES ('pending');
COMMIT;
// Транзакция 1 снова выполняет тот же запрос:
SELECT COUNT(*) FROM orders WHERE status = 'pending'; -- Результат: 6 (фантом!)
Уровни изоляции в PostgreSQL
PostgreSQL поддерживает 3 уровня изоляции (хотя 4 названия в SQL стандарте):
1. READ UNCOMMITTED
Connection conn = dataSource.getConnection();
conn.setTransactionIsolation(
Connection.TRANSACTION_READ_UNCOMMITTED
);
// В PostgreSQL это то же самое, что READ COMMITTED
// Уязвима для: Dirty Read, Non-Repeatable Read, Phantom Read
-- В SQL:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
2. READ COMMITTED (по умолчанию)
Connection conn = dataSource.getConnection();
conn.setTransactionIsolation(
Connection.TRANSACTION_READ_COMMITTED
);
// Исключает: Dirty Read
// Уязвима для: Non-Repeatable Read, Phantom Read
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- Пример фантомного чтения при READ COMMITTED:
BEGIN ISOLATION LEVEL READ COMMITTED;
-- Запрос 1: Найти все активные заказы
SELECT * FROM orders WHERE status = 'active';
-- Результат: 3 заказа (id: 1, 2, 3)
-- Одновременно другой процесс добавляет заказ:
INSERT INTO orders (id, status) VALUES (4, 'active');
COMMIT;
-- Запрос 2: Выполнить тот же SELECT
SELECT * FROM orders WHERE status = 'active';
-- Результат: 4 заказа (id: 1, 2, 3, 4) -- ФАНТОМ!
COMMIT;
3. REPEATABLE READ
Connection conn = dataSource.getConnection();
conn.setTransactionIsolation(
Connection.TRANSACTION_REPEATABLE_READ
);
// Исключает: Dirty Read, Non-Repeatable Read, Phantom Read
// PostgreSQL реализует это через Snapshot Isolation
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- В PostgreSQL на REPEATABLE READ нет фантомного чтения!
BEGIN ISOLATION LEVEL REPEATABLE READ;
-- Создаётся снимок данных ДА НАЧАЛО ТРАНЗАКЦИИ
SELECT * FROM orders WHERE status = 'active';
-- Результат: 3 заказа
-- Другой процесс добавляет и коммитит:
INSERT INTO orders (status) VALUES ('active');
COMMIT;
-- Запрос повторяется:
SELECT * FROM orders WHERE status = 'active';
-- Результат: всё ещё 3 заказа (видим снимок от начала транзакции)
-- НЕТ фантомного чтения!
COMMIT;
4. SERIALIZABLE
Connection conn = dataSource.getConnection();
conn.setTransactionIsolation(
Connection.TRANSACTION_SERIALIZABLE
);
// Исключает: ВСЕ аномалии (Dirty Read, Non-Repeatable Read, Phantom Read)
// Самый строгий уровень
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Трансакции выполняются как будто последовательно
-- Фантомного чтения ТОЧНО нет
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT * FROM orders WHERE status = 'active';
-- Результат: 3 заказа
COMMIT; -- Если другой процесс уже коммитил INSERT,
-- эта транзакция откатится!
Таблица аномалий по уровням
Уровень изоляции | Dirty Read | Non-Rep Read | Phantom Read
---------------------------------------------------------------------------
READ UNCOMMITTED | ДА | ДА | ДА
READ COMMITTED | НЕТ | ДА | ДА
REPEATABLE READ | НЕТ | НЕТ | НЕТ (в PG!)
SERIALIZABLE | НЕТ | НЕТ | НЕТ
PostgreSQL особенность: Snapshot Isolation
PostgreSQL реализует REPEATABLE READ используя Snapshot Isolation (SI), а не двумерные блокировки. Это означает:
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
public class OrderService {
@Transactional(isolation = Isolation.REPEATABLE_READ)
public List<Order> getActiveOrders() {
// В начале транзакции создаётся snapshot всех данных
// Все последующие SELECT видят этот snapshot
// Изменения других транзакций НЕ видны
// Поэтому: ФАНТОМНОГО ЧТЕНИЯ НЕТ
return orderRepository.findByStatus("active");
}
}
Пример в JDBC
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
public class IsolationLevelExample {
public static void demonstrateRepeatableReadNoPhantom() {
try (Connection conn = getConnection()) {
// Установить REPEATABLE READ
conn.setTransactionIsolation(
Connection.TRANSACTION_REPEATABLE_READ
);
conn.setAutoCommit(false);
// Первый SELECT
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery(
"SELECT COUNT(*) FROM orders WHERE status = 'active'"
);
rs.next();
int count1 = rs.getInt(1);
System.out.println("First count: " + count1);
}
// Ждём, пока другой процесс добавит строку
Thread.sleep(2000);
// Второй SELECT на том же уровне
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery(
"SELECT COUNT(*) FROM orders WHERE status = 'active'"
);
rs.next();
int count2 = rs.getInt(1);
System.out.println("Second count: " + count2);
// В PostgreSQL: count1 == count2 (нет фантома)
}
conn.commit();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Spring Data JPA с правильным уровнем
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
public interface OrderRepository extends JpaRepository<Order, Long> {
@Transactional(isolation = Isolation.REPEATABLE_READ)
@Query("SELECT o FROM Order o WHERE o.status = :status")
List<Order> findByStatus(String status);
@Transactional(isolation = Isolation.SERIALIZABLE)
@Query("SELECT o FROM Order o FOR UPDATE")
List<Order> findAllForUpdate();
}
Практический сценарий
public class OrderProcessing {
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void processOrders() {
// Инициализируется snapshot
List<Order> orders = orderRepository.findByStatus("pending");
if (orders.isEmpty()) {
return;
}
// Даже если другой процесс добавит заказы со статусом 'pending',
// мы их не увидим (Snapshot Isolation)
for (Order order : orders) {
processOrder(order);
}
// Проверка в конце транзакции
List<Order> recheck = orderRepository.findByStatus("pending");
// recheck.size() == orders.size() (гарантированно в PG)
}
}
Когда использовать SERIALIZABLE
public class FinancialTransaction {
@Transactional(isolation = Isolation.SERIALIZABLE)
public void transferMoney(String fromAccount, String toAccount,
BigDecimal amount) {
// Требуется максимальная изоляция
Account from = accountRepository.findById(fromAccount).orElseThrow();
Account to = accountRepository.findById(toAccount).orElseThrow();
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException();
}
from.withdraw(amount);
to.deposit(amount);
accountRepository.save(from);
accountRepository.save(to);
// Если другая транзакция читала эти счета, может быть откат
}
}
Конфигурация в Spring Boot
# application.properties
spring.datasource.hikari.isolation-level=READ_COMMITTED
# Или программно:
spring.jpa.properties.hibernate.jdbc.batch_size=20
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setIsolationLevel(
Connection.TRANSACTION_REPEATABLE_READ
);
return dataSource;
}
}
Мониторинг транзакций
-- Посмотреть текущие транзакции
SELECT * FROM pg_stat_activity
WHERE state = 'active';
-- Проверить уровень изоляции
SHOW transaction_isolation;
-- Посмотреть блокировки
SELECT * FROM pg_locks WHERE NOT granted;
Заключение
Правильный ответ:
В PostgreSQL фантомное чтение отсутствует на:
-
REPEATABLE READ — благодаря Snapshot Isolation (SI)
- Это реальное преимущество PostgreSQL
- Не требует блокировок для чтения
- Высокая параллельность
-
SERIALIZABLE — наиболее строгий уровень
- Использует SSI (Serializable Snapshot Isolation)
- Может откатить транзакции в конфликте
- Редко используется
В Java разработке:
- По умолчанию используется READ_COMMITTED
- Для критичных операций: REPEATABLE_READ
- Для финансовых операций: SERIALIZABLE
Это важная компетенция при работе с базами данных в production системах.