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

Какой уровень изоляции транзакций по умолчанию в Spring?

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

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

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

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

Уровень изоляции транзакций в Spring по умолчанию

Уровень изоляции транзакций в Spring по умолчанию — ISOLATION_DEFAULT, который использует уровень изоляции базы данных по умолчанию. Для большинства БД это READ_COMMITTED.

Значение ISOLATION_DEFAULT

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Transactional                    // ISOLATION_DEFAULT (по умолчанию)
public void myMethod() {
    // Использует isolation уровень БД по умолчанию
}

@Transactional(
    isolation = Isolation.DEFAULT  // Явное указание DEFAULT
)
public void explicitDefault() {
    // Эквивалентно предыдущему методу
}

Уровни изоляции в Spring

Spring предоставляет перечисление Isolation с пятью уровнями:

public enum Isolation {
    DEFAULT(-1),              // Уровень БД по умолчанию
    READ_UNCOMMITTED(1),      // Самый низкий уровень (грязные чтения)
    READ_COMMITTED(2),        // Средний уровень (большинство БД по умолчанию)
    REPEATABLE_READ(4),       // Высокий уровень
    SERIALIZABLE(8);          // Самый высокий уровень (полная изоляция)
}

Четыре уровня изоляции ACID

1. READ_UNCOMMITTED (Самый низкий — уровень 0)

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void readUncommittedExample() {
    // Проблемы:
    // 1. Dirty Reads (грязные чтения)
    // 2. Non-repeatable Reads
    // 3. Phantom Reads
}

// Пример: Dirty Read
Transaction 1:                  Transaction 2:
                               SELECT balance FROM account  
                               // Может прочитать незакоммиченное изменение
UPDATE account SET balance = 500
(Не закоммичено ещё)           
                               // Видит balance = 500 (грязное чтение!)
                               SELECT balance FROM account
ROLLBACK                        
                               // balance вернулся на старое значение
                               // но Транзакция 2 уже использовала 500

2. READ_COMMITTED (По умолчанию для большинства БД)

@Transactional(isolation = Isolation.READ_COMMITTED)
public void readCommittedExample() {
    // Предотвращает: Dirty Reads ✓
    // НО позволяет: Non-repeatable Reads, Phantom Reads
}

// Пример: Non-repeatable Read
Transaction 1:                  Transaction 2:
SELECT balance FROM account    
// Результат: 1000            
                               UPDATE account SET balance = 500
                               COMMIT
SELECT balance FROM account
// Результат: 500 (Изменилось во время транзакции T1!)
// Это non-repeatable read - одно SELECT вернул разные результаты

3. REPEATABLE_READ (Для PostgreSQL, MySQL с InnoDB)

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void repeatableReadExample() {
    // Предотвращает: Dirty Reads ✓, Non-repeatable Reads ✓
    // НО позволяет: Phantom Reads
}

// Пример: Phantom Read
Transaction 1:                     Transaction 2:
SELECT * FROM orders WHERE status=pending
// Результат: 5 заказов       
                                  INSERT INTO orders VALUES (6, pending)
                                  COMMIT
SELECT * FROM orders WHERE status=pending
// Результат: 6 заказов! (Появился новый заказ - phantom read)

4. SERIALIZABLE (Самый высокий)

@Transactional(isolation = Isolation.SERIALIZABLE)
public void serializableExample() {
    // Предотвращает: Все проблемы ✓
    // Но: Наибольшее снижение производительности
    // Транзакции выполняются как будто последовательно
}

// Полная изоляция - выполняется как монопоточно
Transaction 1:                     Transaction 2:
BEGIN                              
                                  BEGIN (ждёт завершения T1)
SELECT balance FROM account
// Может читать/писать               
                                  
COMMIT
                                  // Теперь T2 может начать
                                  SELECT balance FROM account

Матрица проблем и уровней изоляции

ПроблемаDescREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE
Dirty ReadЧтение незакоммиченных изменений
Non-repeatable ReadОдно SELECT вернул разные результаты
Phantom ReadНовые строки появились во время транзакции

Какой level используется по умолчанию в разных БД?

MySQL (InnoDB)        → REPEATABLE_READ
PostgreSQL            → READ_COMMITTED
Oracle                → READ_COMMITTED
SQL Server            → READ_COMMITTED
SQLite                → SERIALIZABLE (по типу)

Поэтому, когда ты используешь @Transactional без указания isolation, Spring берёт значение по умолчанию из БД:

// На PostgreSQL это будет READ_COMMITTED
// На MySQL это будет REPEATABLE_READ
@Transactional
public void defaultIsolation() {
    // ...
}

Пример: Явное указание уровня изоляции

import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Isolation;

@Service
public class BankService {
    
    // Низкий уровень (риск грязных чтений, но быстро)
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void fastButRiskyOperation() {
        // Используется когда скорость важнее точности
    }
    
    // По умолчанию (компромисс между скоростью и безопасностью)
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void normalOperation() {
        // Рекомендуется для большинства приложений
    }
    
    // Высокий уровень (гарантирует консистентность, но медленно)
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void safeLongRunningOperation() {
        // Используется для критичных операций
    }
    
    // Максимальная безопасность (но с риском deadlock-ов)
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void criticalFinancialOperation() {
        // Используется РЕДКО - даже в финансовых системах
    }
}

Практический пример: Банковская операция

@Service
public class TransferService {
    
    @Autowired
    private AccountRepository accountRepository;
    
    // ✓ Правильно для transfer: READ_COMMITTED недостаточна!
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        // Читаем баланс
        Account from = accountRepository.findById(fromId);
        Account to = accountRepository.findById(toId);
        
        // Проверяем баланс
        if (from.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException();
        }
        
        // UPDATE-ы
        from.setBalance(from.getBalance().subtract(amount));
        to.setBalance(to.getBalance().add(amount));
        
        accountRepository.save(from);
        accountRepository.save(to);
        
        // С REPEATABLE_READ:
        // - Другой поток не может изменить баланс между SELECT и UPDATE
        // - Гарантируется консистентность денег
    }
}

Spring Configuration для всех транзакций

import org.springframework.context.annotation.Bean;
import org.springframework.data.transaction.ChainedTransactionManager;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    
    // По умолчанию: READ_COMMITTED
    @Bean
    public PlatformTransactionManager transactionManager(
        DataSource dataSource
    ) {
        return new DataSourceTransactionManager(dataSource);
    }
}

Проверка текущего уровня изоляции

@Transactional
public void checkIsolationLevel() {
    Connection conn = // получить connection
    
    int isolationLevel = conn.getTransactionIsolation();
    
    switch (isolationLevel) {
        case Connection.TRANSACTION_READ_UNCOMMITTED:
            System.out.println("READ_UNCOMMITTED");
            break;
        case Connection.TRANSACTION_READ_COMMITTED:
            System.out.println("READ_COMMITTED"); // Вероятно, это
            break;
        case Connection.TRANSACTION_REPEATABLE_READ:
            System.out.println("REPEATABLE_READ");
            break;
        case Connection.TRANSACTION_SERIALIZABLE:
            System.out.println("SERIALIZABLE");
            break;
    }
}

Best Practices

  1. Для большинства приложений: используй ISOLATION_DEFAULT (READ_COMMITTED)
  2. Для критичных операций: используй REPEATABLE_READ или SERIALIZABLE
  3. Избегай READ_UNCOMMITTED: грязные чтения очень опасны
  4. Документируй выбор: почему выбран конкретный уровень
  5. Тестируй с race conditions: проверяй поведение с параллельными транзакциями
  6. Помни о deadlock-ах: более высокие уровни → больше блокировок → риск deadlock

Заключение

По умолчанию Spring использует ISOLATION_DEFAULT, который делегирует выбор уровня изоляции базе данных. Для большинства БД это READ_COMMITTED — хороший компромисс между безопасностью и производительностью. Для критичных операций (особенно финансовых) нужно явно указать более высокий уровень изоляции.