Sobes.tech
Назад к вопросам
Senior
8

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

Компании, где спрашивали
Т-БанкТ-Банк

Ответ от нейросети

sobes.tech AI

Чтобы избежать потери операций при конкурентном доступе к счёту (например, при одновременном списании и начислении), нужно обеспечить атомарность и согласованность обновлений. Основные подходы:

  • Использование транзакций с блокировками: В Java с базой данных можно использовать транзакции с уровнем изоляции SERIALIZABLE или PESSIMISTIC LOCKING (например, SELECT ... FOR UPDATE), чтобы гарантировать последовательный доступ к записи счёта.

  • Оптимистичная блокировка: Добавить в таблицу поле версии (version), при обновлении проверять, что версия не изменилась. Если изменилась — повторять операцию.

  • Использование атомарных операций базы данных: Например, SQL-запросы вида UPDATE accounts SET balance = balance + ? WHERE id = ? гарантируют, что изменения будут применены корректно без потери данных.

  • **В Java можно использовать synchronized или ReentrantLock для синхронизации доступа в памяти, но это работает только в рамках одного экземпляра приложения. Для распределённых систем лучше использовать блокировки на уровне БД или внешних систем (например, Redis).

Пример атомарного обновления баланса в SQL:

BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1 AND balance >= 100;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

Если баланс недостаточен, операция откатится, предотвращая потерю средств.

В Java с JPA можно использовать:

@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    Account from = entityManager.find(Account.class, fromId, LockModeType.PESSIMISTIC_WRITE);
    Account to = entityManager.find(Account.class, toId, LockModeType.PESSIMISTIC_WRITE);
    if (from.getBalance().compareTo(amount) < 0) {
        throw new InsufficientFundsException();
    }
    from.setBalance(from.getBalance().subtract(amount));
    to.setBalance(to.getBalance().add(amount));
}

Такой подход предотвращает гонки и потерю операций.