Здравствуйте, ·, Вы писали:
·>Здравствуйте, Sinclair, Вы писали:
S>>>>Там ничего неясного нет — просто добавляется ещё один предикат во where, а заказ распадается на reserved и residual.
S>>·>Т.е. в одной транзакции будут roundtrips между субд и приложением для каждого товара?
S>>Нет, это всё можно записать двумя стейтментами на весь заказ, вообще без раундтрипов.
·>Интересно, как это выглядит?
Примерно так:
BEGIN TRANSACTION;
WITH
-- 1) Лочим заказ и одновременно проверяем, что он в draft
ord AS (
SELECT o.orderId
FROM orders o
WHERE o.orderId = @orderId
AND o.state = 'draft'
FOR UPDATE
),
-- Позиции заказа (orderId уже фиксируем через join на ord)
req AS (
SELECT oi.productId, oi.quantity
FROM orderItem oi
JOIN ord ON ord.orderId = oi.orderId
),
-- 2) Лочим строки stock в фиксированном порядке (anti-deadlock)
locked AS (
SELECT s.productId, s.available
FROM stock s
JOIN req r ON r.productId = s.productId
ORDER BY s.productId
FOR UPDATE
),
-- 2b) Проверка all-or-nothing:
-- - заказ был draft (ord существует)
-- - для каждой позиции есть строка stock
-- - available хватает для каждой позиции
chk AS (
SELECT
(SELECT COUNT(*) FROM ord) AS ord_cnt,
(SELECT COUNT(*) FROM req) AS need_cnt,
(SELECT COUNT(*) FROM locked) AS locked_cnt,
(SELECT COUNT(*)
FROM locked l
JOIN req r USING (productId)
WHERE l.available >= r.quantity
) AS ok_cnt
),
-- 3) Обновляем stock только если ВСЁ ОК
upd_stock AS (
UPDATE stock s
SET available = s.available - r.quantity,
reserved = s.reserved + r.quantity
FROM req r, chk
WHERE s.productId = r.productId
AND chk.ord_cnt = 1
AND chk.need_cnt > 0
AND chk.need_cnt = chk.locked_cnt
AND chk.need_cnt = chk.ok_cnt
RETURNING 1
),
-- 4) Меняем статус заказа только если stock реально обновился
upd_order AS (
UPDATE orders o
SET state = 'reserved'
WHERE o.orderId = @orderId
AND EXISTS (SELECT 1 FROM upd_stock)
RETURNING o.orderId, o.state
)
SELECT * FROM upd_order;
COMMIT;
·>И что если кто-то закинет заказ на тысячу позиций?
Будет попытка зарезервировать заказ на тысячу позиций.