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

Что такое уровень изоляции Read Committed?

2.4 Senior🔥 101 комментариев
#Базы данных и SQL

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

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

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

Уровень изоляции Read Committed (READ_COMMITTED)

Read Committed — это уровень изоляции транзакций в реляционных базах данных (согласно стандарту ANSI SQL). Это второй по строгости уровень изоляции, который обеспечивает компромисс между согласованностью данных и производительностью. При этом уровне транзакция может читать только те данные, которые были зафиксированы (committed) другими транзакциями, но остается уязвимой к некоторым аномалиям.

Четыре уровня изоляции транзакций

  1. Read Uncommitted (наименее строгий) — может читать грязные данные
  2. Read Committed — читает только зафиксированные данные
  3. Repeatable Read — запрещает dirty reads и non-repeatable reads
  4. Serializable (наиболее строгий) — полная изоляция

Гарантии Read Committed

✅ ЧТО РАЗРЕШАЕТ Read Committed

// Читать только зафиксированные (committed) данные
// Избегать "грязного чтения" (dirty reads)

❌ ЧТО НЕ РАЗРЕШАЕТ Read Committed

// Защита от non-repeatable reads (phantom reads)
// Защита от lost updates (потерь обновлений)

Проблема: Dirty Reads (РЕШЕНА)

Dirty Read — это чтение данных, которые еще не зафиксированы другой транзакцией.

-- Транзакция 1
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- balance изменился с 1000 на 900, но транзакция еще открыта

-- Транзакция 2 (Read Uncommitted уровень)
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE id = 1;  -- ГРЯЗНОЕ ЧТЕНИЕ: 900
COMMIT;

-- Транзакция 1
ROLLBACK;  -- Откатывают изменение
-- Теперь транзакция 2 прочитала несуществующее значение!

-- Read Committed ПРЕДОТВРАЩАЕТ это
-- Транзакция 2 не может прочитать 900, пока транзакция 1 не зафиксирует изменение

Проблема: Non-Repeatable Reads (НЕ РЕШЕНА)

Non-Repeatable Read — это когда одна транзакция читает одно значение, а потом читает его снова и получает другой результат.

-- Транзакция 1
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE id = 1;  -- Результат: 1000
-- (пока транзакция 1 открыта)

-- Транзакция 2 (изменяет данные)
BEGIN TRANSACTION;
UPDATE accounts SET balance = 900 WHERE id = 1;
COMMIT;  -- Зафиксирует изменение

-- Транзакция 1
SELECT balance FROM accounts WHERE id = 1;  -- Результат: 900 (изменился!)
COMMIT;

-- Проблема: в одной транзакции мы прочитали разные значения одного поля
-- Read Committed НЕ защищает от этого!

Проблема: Phantom Reads (НЕ РЕШЕНА)

Phantom Read — это когда строки, которые соответствуют условию WHERE, появляются или исчезают между двумя чтениями в одной транзакции.

-- Транзакция 1
BEGIN TRANSACTION;
SELECT COUNT(*) FROM employees WHERE salary > 50000;
-- Результат: 5 сотрудников

-- Транзакция 2
BEGIN TRANSACTION;
INSERT INTO employees VALUES (6, 'John', 60000);
COMMIT;

-- Транзакция 1
SELECT COUNT(*) FROM employees WHERE salary > 50000;
-- Результат: 6 сотрудников (появился новый!)
COMMIT;

-- Проблема: набор строк изменился между двумя SELECT
// Read Committed НЕ защищает от этого!

Пример: Потеря обновлений (Lost Update)

-- Сценарий: Двое агентов обновляют баланс счета одновременно

-- Начальное значение: balance = 1000

-- Транзакция 1 (Агент 1)
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE id = 1;  -- Результат: 1000
UPDATE accounts SET balance = balance + 500 WHERE id = 1;  -- balance = 1500
COMMIT;

-- Транзакция 2 (Агент 2) выполняется параллельно
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE id = 1;  -- Результат: 1000 (еще не видит обновление)
UPDATE accounts SET balance = balance + 200 WHERE id = 1;  -- Вычисляет 1000 + 200 = 1200
COMMIT;  -- Зафиксирует balance = 1200

-- Финальный результат: balance = 1200
// ПРОБЛЕМА: Потеряно добавление 500!
// Должно было быть: 1000 + 500 + 200 = 1700

Как избежать Lost Updates при Read Committed

Решение 1: SELECT FOR UPDATE

-- Транзакция 1
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;  -- Блокирует строку
-- Другие транзакции не могут менять эту строку!
UPDATE accounts SET balance = balance + 500 WHERE id = 1;
COMMIT;

-- Транзакция 2 должна ждать, пока транзакция 1 завершится

Решение 2: Версионирование (Optimistic Locking)

// Таблица с версией
// CREATE TABLE accounts (
//     id INT PRIMARY KEY,
//     balance DECIMAL,
//     version INT
// );

BEGIN TRANSACTION;
-- Получаем текущую версию
SELECT balance, version FROM accounts WHERE id = 1;  -- balance = 1000, version = 5

-- Обновляем и увеличиваем версию
UPDATE accounts 
SET balance = balance + 500, version = version + 1
WHERE id = 1 AND version = 5;  -- Проверяем версию!

-- Если версия изменилась (другая транзакция обновила), UPDATE не выполнится
COMMIT;

Решение 3: Pessimistic Locking

// Используя JPA
@Entity
@Table(name = "accounts")
public class Account {
    @Id
    private Long id;
    private BigDecimal balance;
    
    @Version
    private Long version;
}

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)  // Пессимистическая блокировка
    @Query("SELECT a FROM Account a WHERE a.id = :id")
    Optional<Account> findByIdWithLock(@Param("id") Long id);
}

@Transactional(isolation = Isolation.READ_COMMITTED)
public void transferMoney(Long accountId, BigDecimal amount) {
    Account account = accountRepository.findByIdWithLock(accountId).get();
    account.setBalance(account.getBalance().add(amount));
    accountRepository.save(account);
}

Пример использования в Java

import java.sql.*;

public class ReadCommittedExample {
    
    public static void main(String[] args) throws Exception {
        Connection conn = DriverManager.getConnection(
            "jdbc:postgresql://localhost:5432/mydb", 
            "user", 
            "password"
        );
        
        // Установить уровень изоляции
        conn.setTransactionIsolation(
            Connection.TRANSACTION_READ_COMMITTED
        );
        
        conn.setAutoCommit(false);
        
        try {
            PreparedStatement stmt = conn.prepareStatement(
                "SELECT balance FROM accounts WHERE id = ? FOR UPDATE"
            );
            stmt.setInt(1, 1);
            ResultSet rs = stmt.executeQuery();
            
            if (rs.next()) {
                BigDecimal balance = rs.getBigDecimal("balance");
                BigDecimal newBalance = balance.add(BigDecimal.valueOf(500));
                
                PreparedStatement updateStmt = conn.prepareStatement(
                    "UPDATE accounts SET balance = ? WHERE id = ?"
                );
                updateStmt.setBigDecimal(1, newBalance);
                updateStmt.setInt(2, 1);
                updateStmt.executeUpdate();
            }
            
            conn.commit();  // Зафиксировать транзакцию
        } catch (SQLException e) {
            conn.rollback();  // Откатить при ошибке
            e.printStackTrace();
        } finally {
            conn.close();
        }
    }
}

Сравнение уровней изоляции

┌──────────────────────┬──────────────────┬──────────────┬─────────────┐
│ Уровень изоляции     │ Dirty Read       │ Non-Rep Read │ Phantom Read│
├──────────────────────┼──────────────────┼──────────────┼─────────────┤
│ Read Uncommitted     │ ✅ Возможен      │ ✅ Возможен  │ ✅ Возможен │
│ Read Committed       │ ❌ Невозможен    │ ✅ Возможен  │ ✅ Возможен │
│ Repeatable Read      │ ❌ Невозможен    │ ❌ Невозможен│ ✅ Возможен │
│ Serializable         │ ❌ Невозможен    │ ❌ Невозможен│ ❌ Невозможен│
└──────────────────────┴──────────────────┴──────────────┴─────────────┘

Когда использовать Read Committed

✅ ИСПОЛЬЗУЙ Read Committed когда:

  • Нужна хорошая производительность
  • Приложение может обрабатывать phantom reads
  • Большинство операций — это чтение
  • OLTP приложения (Online Transaction Processing)

❌ НЕ ИСПОЛЬЗУЙ Read Committed когда:

  • Критична абсолютная согласованность
  • Часто бывают conflicting updates
  • Нужна серийная обработка транзакций
  • Аналитические запросы требуют стабильности (OLAP)

Best Practices

  1. Понимай уровень изоляции своей БД
  2. Используй SELECT FOR UPDATE для критичных данных
  3. Реализуй оптимистическое блокирование (@Version в JPA)
  4. Минимизируй время открытой транзакции
  5. Тестируй race conditions в многопоточной среде
  6. Документируй конфликтные операции

Read Committed — это стандартный уровень изоляции в большинстве современных БД (PostgreSQL, MySQL, Oracle). Он предлагает разумный баланс между производительностью и надежностью, но требует внимательного управления при работе с конфликтующими обновлениями.