Если вы хотите узнать, какой уровень изоляции заставит пример кода работать как есть, а не как лучше всего решить проблему, решаемую примером кода, вам потребуются гарантии как минимум REPEATABLE READ .
Базы данных, которые используют строгую двухфазную блокировку (S2PL) для параллелизма, позволяют транзакциям READ COMMITTED отбрасывать общие блокировки при завершении каждого оператора или даже раньше, поэтому между временем транзакции A проверяется доступность и временем, когда она запрашивает места кто-то другой может выполнить транзакцию B и прочитать снова, не вызывая сбой ни одной из транзакций. Транзакция A может на короткое время заблокировать транзакцию B, но обе обновятся, и вы можете быть перепроданы.
В базах данных, использующих многоверсионное управление параллелизмом (MVCC) для параллелизма, чтения не блокируют записи, а записи не блокируют чтения. В READ COMMITTED каждый оператор использует новый снимок базы данных, основанный на том, что зафиксировано, и, по крайней мере, в некоторых (я знаю, что это верно в PostgreSQL), одновременные записи разрешаются без ошибок. Таким образом, даже если транзакция A находилась в процессе обновления проданного счета или сделала это и не была зафиксирована, транзакция B увидит старый счет и перейдет к обновлению. Когда он попытался обновить, он мог заблокировать ожидание предыдущего обновления, но как только это произойдет, он найдет новую версию строки, проверит, соответствует ли она критериям выбора, обновит, если это так, и проигнорирует строку, если нет, и приступить к фиксации без ошибок. Итак, вы снова перепроданы.
Полагаю, это ответит на вопрос Q2, если вы решите использовать изоляцию транзакции. Эту проблему можно решить на более низком уровне изоляции, изменив пример кода для получения явных блокировок, но обычно это приводит к большей блокировке, чем при использовании уровня изоляции, который является достаточно строгим для его автоматической обработки.