← Назад к вопросам

Какие знаешь аномалии на уровнях изоляции между транзакциями?

2.0 Middle🔥 71 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Аномалии на уровнях изоляции транзакций

Это один из самых важных вопросов про многопоточность и работу с БД. Аномалии возникают, когда две и более транзакции выполняются одновременно и их операции пересекаются. Разные уровни изоляции позволяют избежать разных аномалий, но с разной стоимостью производительности.

Основные аномалии

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 ReadNon-RepeatablePhantomWrite 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);
}

Выводы

  1. Dirty Read — самая опасная, исключается на READ COMMITTED
  2. Non-Repeatable Read — частая в веб-приложениях, исключается на REPEATABLE READ
  3. Phantom Read — редкая, но критичная, исключается на SERIALIZABLE
  4. Write Skew — самая коварная, нужно использовать блокировки (SELECT FOR UPDATE) или SERIALIZABLE
  5. По умолчанию используй READ COMMITTED, повышай уровень только если нужен
  6. Тестируй аномалии — создавай тесты с многопоточностью и параллельными транзакциями
Какие знаешь аномалии на уровнях изоляции между транзакциями? | PrepBro