Как называется проблема неконсистентных данных при параллельных транзакциях
Что проверяют
Знаете ли вы точные названия аномалий параллельных транзакций и связываете ли их с уровнями изоляции. Ответ «race condition» слишком общий. Следом спрашивают, какой уровень изоляции у вашей базы по умолчанию и как вы боролись с аномалией в коде.
Как отвечать
Общее название: аномалии конкурентного доступа, то есть нарушение изоляции, буквы I в ACID. Конкретные случаи:
- dirty read: транзакция видит незакоммиченные изменения другой, которые потом откатятся;
- non-repeatable read: повторное чтение той же строки даёт другое значение, её успели изменить и закоммитить;
- phantom read: повторный запрос по условию возвращает новые строки;
- lost update: две транзакции прочитали значение, обе изменили, вторая запись затёрла первую;
- write skew: каждая транзакция корректна сама по себе, а вместе они нарушают инвариант. Классика: два врача одновременно снимаются с дежурства.
Уровни по стандарту SQL: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE. PostgreSQL по умолчанию работает на READ COMMITTED, MySQL InnoDB на REPEATABLE READ. Поведение зависит от реализации: в PostgreSQL REPEATABLE READ построен на снимке и фантомов не допускает, а write skew ловит только SERIALIZABLE.
Инструменты в Java-стеке:
- уровень изоляции:
@Transactional(isolation = Isolation.SERIALIZABLE)в Spring илиConnection.setTransactionIsolationв JDBC; - оптимистическая блокировка: поле
@Versionв JPA, при конфликтеOptimisticLockExceptionи повтор операции; - пессимистическая:
@Lock(LockModeType.PESSIMISTIC_WRITE), под капотомSELECT ... FOR UPDATE; - атомарный апдейт:
UPDATE account SET balance = balance - ? WHERE id = ?вместо чтения в Java и записи обратно.
Где спотыкаются
Путают гонку в памяти JVM с аномалиями базы и предлагают synchronized. При нескольких экземплярах сервиса он не защищает ничего.
Считают, что SERIALIZABLE решает всё бесплатно. На нём база откатывает конфликтующие транзакции с ошибкой сериализации, и без логики повтора пользователь получит 500.
Не знают уровень изоляции своей базы по умолчанию и уверенно говорят, что non-repeatable read у них невозможен.