Можно использовать оптимистичную блокировку вида
UPDATE items SET amount = :new_amount WHERE amount = :expected_amount AND id = :item_id
И проверять, что в результате обновилась одна строка и повторять в случае если не обновилась.
Либо делать
SELECT * from items where id = :item_id FOR UPDATE в рамках транзакции, чтобы заблокировать конкретную строку.
Либо можно попробовать что-то типа
UPDATE items SET amount = amount - 1 WHERE id = :item_id
чтобы вычисление нового количества произошло также атомарно.
Либо использовать схему с event sourcing, как предложил
constantinesx и хранить только события "такой-то пользователь просканировал такой-то товар" и потом уже отдельно считать агрегированное значение (есть разные способы)
Либо можно упороться и хранить каждый экземпляр товара как отдельную строку в таблице, но это вырастет в очевидную проблему, если на складе хранится очень много одинаковых товаров.
Чтобы выбрать наилучший вариант - надо смотреть на систему полностью, а не на один маленький кусочек в рамках вопроса.