Для чего нужен каждый уровень изоляции БД?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Уровни изоляции транзакций БД
Введение
Уровни изоляции (isolation levels) определяют, как одновременно выполняющиеся транзакции видят изменения друг друга. Они решают проблему баланса между консистентностью данных и производительностью.
Четыре уровня изоляции (от низкого к высокому)
1. READ UNCOMMITTED (Чтение незафиксированных данных)
Назначение: Максимальная производительность за счет консистентности.
Проблемы:
- Dirty Read — транзакция может читать данные, которые еще не зафиксированы другой транзакцией
// Транзакция A
setAutoCommit(false);
PreparedStatement stmt = conn.prepareStatement("UPDATE accounts SET balance = balance + 1000 WHERE id = 1");
stmt.executeUpdate();
// Не выполняем commit
// Транзакция B (с READ UNCOMMITTED)
Connection connB = dataSource.getConnection();
connB.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
PreparedStatement readStmt = connB.prepareStatement("SELECT balance FROM accounts WHERE id = 1");
ResultSet rs = readStmt.executeQuery();
// Видит увеличенный баланс, хотя трансакция A еще не выполнила commit!
Когда использовать: Практически никогда. Используется только для отчетов, не критичных к точности.
2. READ COMMITTED (Чтение зафиксированных данных)
Назначение: Баланс между консистентностью и производительностью.
Решаемые проблемы:
- Dirty Read не возможен — читаются только зафиксированные данные
Оставшиеся проблемы:
- Non-Repeatable Read — одна строка может измениться между двумя чтениями в одной транзакции
// Транзакция A
Connection connA = dataSource.getConnection();
connA.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
PreparedStatement select1 = connA.prepareStatement("SELECT balance FROM accounts WHERE id = 1");
ResultSet rs1 = select1.executeQuery();
rs1.next();
int balance1 = rs1.getInt("balance"); // Значение: 1000
// Здесь другая транзакция B изменяет и коммитит запись
PreparedStatement select2 = connA.prepareStatement("SELECT balance FROM accounts WHERE id = 1");
ResultSet rs2 = select2.executeQuery();
rs2.next();
int balance2 = rs2.getInt("balance"); // Значение: 2000 (изменилось!)
Когда использовать: Это уровень по умолчанию в большинстве БД. Подходит для большинства приложений.
3. REPEATABLE READ (Повторяемое чтение)
Назначение: Гарантирует, что данные внутри транзакции не изменяются.
Решаемые проблемы:
- Non-Repeatable Read невозможен — строка заблокирована для других транзакций
Оставшиеся проблемы:
- Phantom Read — новые строки, вставленные другой транзакцией, видны в результатах
// Транзакция A
Connection connA = dataSource.getConnection();
connA.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
PreparedStatement select1 = connA.prepareStatement("SELECT COUNT(*) FROM orders WHERE status = active");
ResultSet rs1 = select1.executeQuery();
rs1.next();
int count1 = rs1.getInt(1); // Значение: 10
// Другая транзакция B вставляет новую строку и коммитит
PreparedStatement select2 = connA.prepareStatement("SELECT COUNT(*) FROM orders WHERE status = active");
ResultSet rs2 = select2.executeQuery();
rs2.next();
int count2 = rs2.getInt(1); // Значение: 11 (новая строка появилась!)
Когда использовать: Когда критично, чтобы строки не менялись в рамках транзакции, но добавление новых строк допустимо.
4. SERIALIZABLE (Сериализуемость)
Назначение: Максимальная консистентность. Транзакции выполняются так, как если бы они были одна за другой.
Решаемые проблемы:
- Phantom Read невозможен — вся таблица заблокирована
- Все проблемы предыдущих уровней решены
Недостаток:
- Значительное снижение производительности из-за блокировок
// Транзакция A
Connection connA = dataSource.getConnection();
connA.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
PreparedStatement select1 = connA.prepareStatement("SELECT COUNT(*) FROM orders WHERE status = active");
ResultSet rs1 = select1.executeQuery();
rs1.next();
int count1 = rs1.getInt(1);
// Другие транзакции ЖДУТ, пока эта не завершится
// И не могут вставлять новые строки в таблицу orders
Когда использовать: Только для критичных финансовых операций или когда консистентность важнее производительности.
Таблица сравнения проблем
| Уровень | Dirty Read | Non-Repeatable Read | Phantom Read |
|---|---|---|---|
| READ UNCOMMITTED | ✓ | ✓ | ✓ |
| READ COMMITTED | ✗ | ✓ | ✓ |
| REPEATABLE READ | ✗ | ✗ | ✓ |
| SERIALIZABLE | ✗ | ✗ | ✗ |
Практический пример в Java
@Service
public class AccountService {
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepository.findById(fromId).orElseThrow();
Account to = accountRepository.findById(toId).orElseThrow();
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
}
@Transactional(isolation = Isolation.SERIALIZABLE)
public void criticalPaymentOperation(Long accountId) {
// Для очень критичных операций используем SERIALIZABLE
}
}
Рекомендации
- Начните с READ COMMITTED — это хороший компромисс для большинства приложений
- Используйте REPEATABLE READ, если нужна консистентность строк
- Избегайте SERIALIZABLE без необходимости — это сильно влияет на производительность
- Продумайте логику приложения — часто правильно разработанная бизнес-логика может компенсировать более низкий уровень изоляции
- Мониторьте deadlocks — при высоких уровнях изоляции они становятся более вероятными