Скорее этот вопрос задан с целю, понять как правильно делать подобные простые счетчики.
Правильно - это выполнение операции в одном запросе. В вашем случае это будет
UPDATE warehouse
SET balance = balance - 1
WHERE product_id = 5
AND balance > 0;
Такая одиночная форма запроса, во-первых, гарантирует, что при обновлении будет использовано строго то значение, которое прочитано (т.е. никто не влезет между чтением старого значения и записью нового), во-вторых, не даст "уйти в минус".
Если процедура не укладывается в один запрос, то выполняющие операцию запросы должны оформляться в транзакцию. С подбором правильного уровня изоляции, который исключит нежелательные интерференции и искажение данных. Есть и упрощённый способ - это использование блокировок. Но там слишком много сложностей, и слишком много надо учитывать, да и на производительности сказывается совсем не лучшим образом.
Можно (хоть и нежелательно) это делать и "на ручной тяге". Например, если вы хотите всё же использовать трёхзапросную форму, как в вопросе, то (схематично) будет что-то вроде
SET @balance = (SELECT balance FROM warehouse WHERE product_id = 5);
SET @new_balance = @balance - 1;
UPDATE warehouse
SET balance = @new_balance
WHERE product_id = 5
AND balance = @balance;
Обратите внимание на дополнительное условие в последнем запросе. Оно проверяет, что с момента запроса старого значения и до попытки записать новое никто не внёс изменений в данные. Если же кто-то подсуетился и успел, то последний запрос обновит ноль записей, что и будет маркером, что операция корректировки баланса неудачна и надо повторить попытку (впрочем, ноль получится и в случае, если кто-то удалит запись для этого продукта - тогда никакие повторения не помогут).