← Назад к вопросам
Какие аномалии изоляций транзакций не могут случаться
2.8 Senior🔥 101 комментариев
#Базы данных и SQL#Многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие аномалии изоляций транзакций не могут случаться
В реляционных БД существуют уровни изоляции транзакций, которые предотвращают различные аномалии. Рассмотрю, какие аномалии возможны и какие нет при разных уровнях.
Основные аномалии транзакций
1. Dirty Read (грязное чтение)
// Одна транзакция читает неподтвержденные (грязные) данные от другой
// Транзакция 1 (не подтверждена)
UPDATE users SET balance = 1000 WHERE id = 1;
// balance = 1000 (но ROLLBACK еще не произошел)
// Транзакция 2 (читает грязное значение)
SELECT balance FROM users WHERE id = 1; // Видит 1000
// Транзакция 1 откатывается
ROLLBACK; // balance вернулся к 500
// Транзакция 2 видела 1000, но это не реально!
Предотвращается: READ COMMITTED и выше
2. Non-Repeatable Read (неповторяемое чтение)
// Транзакция читает одни данные дважды и получает разные результаты
// Транзакция 1
BEGIN;
SELECT balance FROM users WHERE id = 1; // 500
// Транзакция 2 (выполняется между запросами)
UPDATE users SET balance = 600 WHERE id = 1;
COMMIT;
// Транзакция 1 (повторное чтение)
SELECT balance FROM users WHERE id = 1; // 600 (изменилось!)
COMMIT;
// Данные изменились внутри одной транзакции
Предотвращается: REPEATABLE READ и выше
3. Phantom Read (фантомное чтение)
// Транзакция выполняет запрос дважды и получает разное количество строк
// Транзакция 1
BEGIN;
SELECT COUNT(*) FROM users WHERE age > 30; // 5 строк
// Транзакция 2 (выполняется между запросами)
INSERT INTO users (name, age) VALUES ('Bob', 35);
COMMIT;
// Транзакция 1 (повторный запрос)
SELECT COUNT(*) FROM users WHERE age > 30; // 6 строк (фантом!)
COMMIT;
// Новая строка появилась внутри транзакции
Предотвращается: SERIALIZABLE
4. Lost Update (потеря обновления)
// Две транзакции обновляют один и тот же объект
// Транзакция 1: balance = 1000
UPDATE users SET balance = balance + 100 WHERE id = 1;
// balance = 1100
// Транзакция 2: тоже видит balance = 1000
UPDATE users SET balance = balance + 50 WHERE id = 1;
// balance = 1050 (перезаписывает изменения транзакции 1!)
// Итог: balance = 1050, но должно быть 1150
// +100 потеряно!
Предотвращается: REPEATABLE READ и выше (при правильной блокировке)
Уровни изоляции в SQL
-- 1. READ UNCOMMITTED (самый низкий уровень)
-- Предотвращает: ничего
-- Позволяет: Dirty Read, Non-Repeatable Read, Phantom Read
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 2. READ COMMITTED (по умолчанию в большинстве БД)
-- Предотвращает: Dirty Read
-- Позволяет: Non-Repeatable Read, Phantom Read
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 3. REPEATABLE READ (по умолчанию в MySQL)
-- Предотвращает: Dirty Read, Non-Repeatable Read
-- Позволяет: Phantom Read
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 4. SERIALIZABLE (самый высокий уровень)
-- Предотвращает: все аномалии
-- Позволяет: ничего (но медленнее всех)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Java примеры с Spring
// Управление уровнем изоляции в Spring
@Service
public class UserService {
// READ COMMITTED (по умолчанию)
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateBalance(Long userId, BigDecimal amount) {
User user = userRepository.findById(userId).orElseThrow();
user.setBalance(user.getBalance().add(amount));
userRepository.save(user);
}
// REPEATABLE READ для критичных операций
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transferMoney(Long from, Long to, BigDecimal amount) {
User fromUser = userRepository.findById(from).orElseThrow();
User toUser = userRepository.findById(to).orElseThrow();
if (fromUser.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException();
}
fromUser.setBalance(fromUser.getBalance().subtract(amount));
toUser.setBalance(toUser.getBalance().add(amount));
userRepository.saveAll(Arrays.asList(fromUser, toUser));
}
// SERIALIZABLE для очень критичных операций
@Transactional(isolation = Isolation.SERIALIZABLE)
public void processPayment(Long orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
// Гарантирует полную сериализацию
}
}
Таблица предотвращения аномалий
| Уровень | Dirty Read | Non-Repeatable | Phantom | Lost Update |
|---|---|---|---|---|
| READ UNCOMMITTED | ❌ Нет | ❌ Нет | ❌ Нет | ❌ Нет |
| READ COMMITTED | ✅ Да | ❌ Нет | ❌ Нет | 🔶 Частично |
| REPEATABLE READ | ✅ Да | ✅ Да | ❌ Нет | ✅ Да |
| SERIALIZABLE | ✅ Да | ✅ Да | ✅ Да | ✅ Да |
Какие аномалии НЕ МОГУТ случаться при разных уровнях
READ UNCOMMITTED:
- НЕ защищает ничего
- Все аномалии ВОЗМОЖНЫ
READ COMMITTED:
- Dirty Read НЕ МОЖЕТ случиться ✅
- Non-Repeatable Read МОЖЕТ случиться ❌
- Phantom Read МОЖЕТ случиться ❌
REPEATABLE READ:
- Dirty Read НЕ МОЖЕТ случиться ✅
- Non-Repeatable Read НЕ МОЖЕТ случиться ✅
- Phantom Read МОЖЕТ случиться ❌ (в стандарте SQL)
- В некоторых БД (MySQL) Phantom Read тоже предотвращается
SERIALIZABLE:
- Ничего не МОЖЕТ случиться ✅✅✅
- Все аномалии полностью предотвращены
PostgreSQL и MySQL особенности
-- PostgreSQL: REPEATABLE READ уже блокирует phantom reads
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- Phantom read НЕ МОЖЕТ случиться (используется MVCC)
-- MySQL: REPEATABLE READ уже блокирует phantom reads
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- Phantom read НЕ МОЖЕТ случиться (gap locking)
-- Обе БД обрабатывают phantom read по-разному,
-- но оба безопасны на REPEATABLE READ
Рекомендации
// 1. Для большинства операций: READ COMMITTED (по умолчанию)
@Transactional(isolation = Isolation.READ_COMMITTED)
public void normalOperation() { ... }
// 2. Для финансовых операций: REPEATABLE READ
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transferMoney() { ... }
// 3. Для очень критичных: SERIALIZABLE (но медленно!)
@Transactional(isolation = Isolation.SERIALIZABLE)
public void veryImportantOperation() { ... }
// 4. Используй SELECT FOR UPDATE для явной блокировки
@Query("SELECT u FROM User u WHERE u.id = :id FOR UPDATE")
User findByIdForUpdate(@Param("id") Long id);
Правильный выбор уровня изоляции — критичен для безопасности и производительности.