Какие знаешь аномалии на уровнях изоляции между транзакциями?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Аномалии на уровнях изоляции транзакций
Это один из самых важных вопросов про многопоточность и работу с БД. Аномалии возникают, когда две и более транзакции выполняются одновременно и их операции пересекаются. Разные уровни изоляции позволяют избежать разных аномалий, но с разной стоимостью производительности.
Основные аномалии
1. Dirty Read (Грязное чтение)
Определение: Транзакция T1 читает данные, которые были изменены транзакцией T2, но T2 ещё не завершилась (не коммитилась).
Время | T1 (Читатель) | T2 (Писатель)
-----|--------------------------|------------------------
1 | | BEGIN TRANSACTION
2 | | UPDATE users SET balance = 1000 WHERE id = 1
3 | BEGIN TRANSACTION |
4 | SELECT balance FROM users WHERE id = 1 -- Читает 1000
5 | | ROLLBACK (откат!)
6 | Использует balance=1000 | (Но это была откатанная операция!)
Проблема: T1 видит данные, которых не существует в коммитленном состоянии.
На каком уровне изоляции исключается: READ COMMITTED и выше
2. Non-Repeatable Read (Неповторяющееся чтение)
Определение: Транзакция T1 дважды читает одни и те же данные, но получает разные значения, потому что между чтениями T2 обновила эти данные и коммитила изменения.
Время | T1 (Читатель) | T2 (Писатель)
-----|--------------------------|------------------------
1 | BEGIN TRANSACTION |
2 | SELECT balance FROM users WHERE id = 1 -- Результат: 1000
3 | | BEGIN TRANSACTION
4 | | UPDATE users SET balance = 2000 WHERE id = 1
5 | | COMMIT
6 | SELECT balance FROM users WHERE id = 1 -- Результат: 2000 (!)
7 | COMMIT |
Проблема: Одни и те же данные читаются дважды и дают разные результаты внутри одной транзакции.
На каком уровне изоляции исключается: REPEATABLE READ и выше
3. Phantom Read (Фантомное чтение)
Определение: Транзакция T1 дважды выполняет запрос с условием WHERE, но получает разное количество строк, потому что T2 добавила или удалила строки и коммитила изменения.
Время | T1 (Читатель) | T2 (Писатель)
-----|----------------------------------|------------------------
1 | BEGIN TRANSACTION |
2 | SELECT * FROM users WHERE age > 18 -- 100 пользователей
3 | | BEGIN TRANSACTION
4 | | INSERT INTO users VALUES (...) WHERE age > 18
5 | | COMMIT
6 | SELECT * FROM users WHERE age > 18 -- 101 пользователь (!)
7 | COMMIT |
Проблема: Набор строк, удовлетворяющих условию, изменился во время транзакции.
На каком уровне изоляции исключается: SERIALIZABLE
4. Serialization Anomaly (Аномалия сериализации) / Write Skew
Определение: Две транзакции читают одни и те же данные, обновляют разные части и коммитят, нарушая бизнес-правила.
Сценарий: Доктор не может быть один на дежурстве. Минимум 2 доктора должно быть на смену.
Время | T1 (Доктор A выходит) | T2 (Доктор B выходит)
-----|-------------------------------|-----------------------------
1 | BEGIN TRANSACTION |
2 | SELECT COUNT(*) FROM doctors_on_shift -- 2 доктора
3 | | BEGIN TRANSACTION
4 | | SELECT COUNT(*) FROM doctors_on_shift -- 2 доктора
5 | UPDATE doctors_on_shift SET active=0 WHERE id='A'
6 | | UPDATE doctors_on_shift SET active=0 WHERE id='B'
7 | COMMIT |
8 | | COMMIT
9 | Результат: 0 докторов на смене (нарушение правила!)
Проблема: Обе транзакции прошли проверку (было 2 доктора), но нарушили бизнес-инвариант.
На каком уровне изоляции исключается: Только с предыдущего чтения всех данных в SELECT FOR UPDATE
Уровни изоляции и какие аномалии они исключают
| Уровень | Dirty Read | Non-Repeatable | Phantom | Write Skew | Производительность |
|---|---|---|---|---|---|
| READ UNCOMMITTED | ✗ | ✗ | ✗ | ✗ | Максимум |
| READ COMMITTED | ✓ | ✗ | ✗ | ✗ | Высокая |
| REPEATABLE READ | ✓ | ✓ | ✗* | ✗ | Средняя |
| SERIALIZABLE | ✓ | ✓ | ✓ | ✓ | Минимум |
*В некоторых БД (PostgreSQL) REPEATABLE READ тоже исключает phantom read
Детальное описание каждого уровня
READ UNCOMMITTED (Чтение грязных данных)
// PostgreSQL пример
BEGIN TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT * FROM users; // Может читать откатанные изменения
COMMIT;
Гарантии:
- Практически никаких
- Может видеть незакоммиченные изменения
- Может видеть phantom read, non-repeatable read
Используется: Редко, только для очень грязной статистики
READ COMMITTED (Чтение коммитленных данных) — по умолчанию в большинстве БД
// Стандартный уровень в PostgreSQL, MySQL (InnoDB)
BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT * FROM users; // Видит только коммитленные данные
COMMIT;
Гарантии:
- ✓ Исключает dirty read
- ✗ Не исключает non-repeatable read
- ✗ Не исключает phantom read
- ✗ Не исключает write skew
Пример проблемы:
T1: BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
T1: SELECT salary FROM employees WHERE id = 5; -- 100
T2: BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
T2: UPDATE employees SET salary = 200 WHERE id = 5;
T2: COMMIT;
T1: SELECT salary FROM employees WHERE id = 5; -- 200 (неповторяющееся!)
T1: COMMIT;
Когда использовать: Большинство веб-приложений, где нужна высокая производительность
REPEATABLE READ (Повторяющееся чтение)
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM users WHERE age > 18;
COMMIT;
Гарантии:
- ✓ Исключает dirty read
- ✓ Исключает non-repeatable read
- ✗ Может быть phantom read (в стандарте)
- ✗ Не исключает write skew
Механизм: Снимок данных в начале транзакции (MVCC — Multi-Version Concurrency Control)
Пример:
T1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
T1: SELECT salary FROM employees WHERE id = 5; -- 100
-- T1 теперь видит снимок состояния БД
T2: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
T2: UPDATE employees SET salary = 200 WHERE id = 5;
T2: COMMIT;
T1: SELECT salary FROM employees WHERE id = 5; -- ВСЕ ЕЩЁ 100 (повторяется!)
T1: COMMIT;
Когда использовать: Когда нужна согласованность в рамках одной транзакции (отчёты, аналитика)
SERIALIZABLE (Сериализуемое выполнение)
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT * FROM users WHERE age > 18;
COMMIT;
Гарантии:
- ✓ Исключает dirty read
- ✓ Исключает non-repeatable read
- ✓ Исключает phantom read
- ✓ Исключает write skew
Механизм:
- PostgreSQL: Serializable Snapshot Isolation (SSI) + обнаружение конфликтов
- MySQL: Настоящие блокировки
Пример:
T1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
T1: SELECT salary FROM employees WHERE id = 5; -- 100
T2: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
T2: UPDATE employees SET salary = 200 WHERE id = 5; -- БЛОКИРУЕТСЯ!
T1: COMMIT;
T2: -- Теперь может выполниться
T2: COMMIT;
Когда использовать: Критичные операции (платежи, финансы, инвентарь)
Примеры на Java + JDBC
Настройка уровня изоляции в Java:
Connection conn = dataSource.getConnection();
// Установка уровня изоляции
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
try {
conn.setAutoCommit(false);
// Операции
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
conn.commit();
} catch (SQLException e) {
conn.rollback();
throw e;
} finally {
conn.close();
}
Константы Connection:
Connection.TRANSACTION_READ_UNCOMMITTED // Level 0
Connection.TRANSACTION_READ_COMMITTED // Level 1
Connection.TRANSACTION_REPEATABLE_READ // Level 2
Connection.TRANSACTION_SERIALIZABLE // Level 3
Connection.TRANSACTION_NONE // Не поддерживается
Пример проблемы Non-Repeatable Read:
public class NonRepeatableReadDemo {
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection(url, user, password);
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
conn.setAutoCommit(false);
// Первое чтение
Statement stmt1 = conn.createStatement();
ResultSet rs1 = stmt1.executeQuery("SELECT balance FROM accounts WHERE id = 1");
rs1.next();
int balance1 = rs1.getInt("balance"); // 1000
// Другая транзакция обновляет данные и коммитит
Connection conn2 = DriverManager.getConnection(url, user, password);
Statement stmt2 = conn2.createStatement();
stmt2.execute("UPDATE accounts SET balance = 2000 WHERE id = 1");
conn2.commit();
conn2.close();
// Второе чтение в той же транзакции
ResultSet rs2 = stmt1.executeQuery("SELECT balance FROM accounts WHERE id = 1");
rs2.next();
int balance2 = rs2.getInt("balance"); // 2000 (ДРУГОЕ!)
System.out.println("First read: " + balance1); // 1000
System.out.println("Second read: " + balance2); // 2000
conn.commit();
conn.close();
}
}
Рекомендации по выбору уровня изоляции
READ COMMITTED:
- Веб-приложения (REST API, MVC)
- Кэширование снаружи (Redis)
- Чтение + обновление одной строки
- Когда производительность критична
REPEATABLE READ:
- Отчёты и аналитика
- Многошаговые вычисления
- Когда нужна последовательность данных
- MySQL/InnoDB по умолчанию
SERIALIZABLE:
- Финансовые операции
- Платежи и переводы
- Критичные бизнес-правила
- Когда вероятность конфликтов низкая
Взаимодействие с ORM (Hibernate, JPA)
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateAccount(Long id, BigDecimal amount) {
Account account = accountRepository.findById(id);
account.setBalance(account.getBalance().add(amount));
accountRepository.save(account);
}
Выводы
- Dirty Read — самая опасная, исключается на READ COMMITTED
- Non-Repeatable Read — частая в веб-приложениях, исключается на REPEATABLE READ
- Phantom Read — редкая, но критичная, исключается на SERIALIZABLE
- Write Skew — самая коварная, нужно использовать блокировки (SELECT FOR UPDATE) или SERIALIZABLE
- По умолчанию используй READ COMMITTED, повышай уровень только если нужен
- Тестируй аномалии — создавай тесты с многопоточностью и параллельными транзакциями