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

Какие знаешь недостаточно изолированные транзакции при параллельной работе и проблеме с данными?

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

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

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

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

Уровни изоляции транзакций и проблемы параллельного доступа

При одновременной работе нескольких транзакций возникают проблемы с недостаточной изоляцией. В SQL стандарте определены четыре уровня изоляции (ACID принцип) для решения этих проблем.

Основные проблемы (не-изолированные случаи)

1. Dirty Read (Грязное чтение)

Транзакция читает данные, которые были изменены другой транзакцией, но ещё не закоммичены. Если вторая транзакция откатится, первая будет работать с несуществующими данными.

// Транзакция 1
begin;
update accounts set balance = balance - 100 where id = 1;

// Транзакция 2 (одновременно)
begin;
select balance from accounts where id = 1; // читает 900 (не закоммиченное значение)

// Транзакция 1 откатывается (rollback)
// Транзакция 2 работает с неправильным значением

2. Non-Repeatable Read (Неповторяемое чтение)

Транзакция дважды читает одни и те же данные, но получает разные значения, потому что другая транзакция обновила данные и закоммитила изменения.

// Транзакция 1
begin;
select salary from employees where id = 5; // 50000

// Транзакция 2 (одновременно)
begin;
update employees set salary = 60000 where id = 5;
commit;

// Транзакция 1
select salary from employees where id = 5; // 60000 (другое значение!)
commit;

3. Phantom Read (Фантомное чтение)

Транзакция дважды выполняет одинаковый запрос, но получает разное количество строк, потому что другая транзакция вставила или удалила строки.

// Транзакция 1
begin;
select count(*) from products where category = "electronics"; // 10 строк

// Транзакция 2 (одновременно)
begin;
insert into products (category) values ("electronics");
commit;

// Транзакция 1
select count(*) from products where category = "electronics"; // 11 строк
commit;

4. Lost Update (Потерянное обновление)

Две транзакции одновременно обновляют одни и те же данные. Изменения одной транзакции перезаписываются изменениями другой.

// Исходное значение: balance = 1000

// Транзакция 1
begin();
balance = select balance; // 1000
balance -= 100;
update accounts set balance = 900;
commit(); // balance = 900

// Транзакция 2 (одновременно)
begin();
balance = select balance; // 1000
balance += 500;
update accounts set balance = 1500;
commit(); // balance = 1500 (потеря вычитания -100)

Уровни изоляции транзакций

УровеньDirty ReadNon-Rep. ReadPhantom ReadПроизводительность
READ UNCOMMITTED✓ Возможен✓ Возможен✓ ВозможенМаксимальная
READ COMMITTED✓ Возможен✓ ВозможенВысокая
REPEATABLE READ✓ ВозможенСредняя
SERIALIZABLEМинимальная

Реализация в Java/JPA

// 1. Через аннотацию @Transactional
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Isolation;

@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateBalance(Long accountId, BigDecimal amount) {
    Account account = repository.findById(accountId).orElseThrow();
    account.setBalance(account.getBalance().add(amount));
    repository.save(account);
}

// 2. Через JPA с пессимистической блокировкой
import javax.persistence.LockModeType;

public interface AccountRepository extends JpaRepository<Account, Long> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT a FROM Account a WHERE a.id = :id")
    Account findByIdWithLock(@Param("id") Long id);
}

// 3. Оптимистическая блокировка
@Entity
public class Account {
    @Version
    private Long version; // автоматическая версионизация
}

// 4. SELECT FOR UPDATE
@Query(value = "SELECT * FROM accounts WHERE id = :id FOR UPDATE", nativeQuery = true)
Account findByIdForUpdate(@Param("id") Long id);

Рекомендации по выбору уровня

  • READ COMMITTED: оптимален для большинства приложений (баланс между безопасностью и производительностью)
  • REPEATABLE READ: когда критична консистентность одной записи (финансовые операции)
  • SERIALIZABLE: очень редко, только для максимально критичных операций (очень медленно)

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