Tồn kho là quan hệ kho × biến thể, không phải một cột stock trên bảng sản phẩm.
create table inventory (
warehouse_id bigint not null references warehouses(id),
variant_id bigint not null references variants(id),
on_hand int not null default 0 check (on_hand >= 0),
reserved int not null default 0 check (reserved >= 0),
primary key (warehouse_id, variant_id)
);Tách on_hand và reserved. Số bán được là on_hand - reserved. Khi khách đặt hàng thì tăng reserved; khi xuất kho thực tế mới giảm on_hand và giảm reserved tương ứng. Nhờ vậy hai khách không cùng mua được một cái áo cuối cùng, mà cũng không trừ tồn sai khi đơn bị huỷ.
Giữ chỗ phải chạy trong transaction có khoá hàng:
begin;
update inventory
set reserved = reserved + 1
where warehouse_id = $1 and variant_id = $2
and on_hand - reserved >= 1; -- 0 rows affected means sold out
commit;Điều kiện on_hand - reserved >= 1 đặt ngay trong UPDATE khiến database tự kiểm tra dưới khoá dòng, không cần SELECT trước rồi mới ghi.
Hai bổ sung thường được hỏi thêm: bảng inventory_movements ghi mọi lần nhập/xuất để đối soát (tồn hiện tại phải bằng tổng movement), và giữ chỗ có hạn — thêm expires_at cho reservation rồi job dọn định kỳ, tránh giỏ bỏ quên khoá hàng vĩnh viễn.