Skip to content

ADR-0002. Khoá bi quan SELECT FOR UPDATE trên đơn khi thay đổi item

TrườngGiá trị
Trạng tháiAccepted
Ngày2026-02-08
Người quyết địnhPhat Nguyen
Thay thế cho-

Bối cảnh

  • Hai thiết bị (POS-A và POS-B) thường thêm cùng một item vào cùng một đơn DRAFT cùng lúc.
  • Logic gộp tự nhiên - "nếu (orderId, itemType, itemId) đã tồn tại thì tăng số lượng" - có khoảng tranh chấp: cả hai transaction đều thấy chưa có dòng nào, cả hai cùng chèn, và kết quả là item bị trùng.
  • Điều này từng xảy ra trên production khi một khách gọi "burger × 2" trên tablet A và "burger × 1" trên tablet B cùng lúc - kết quả là hai dòng burger riêng biệt thay vì một dòng số lượng 3.

Quyết định

Trong SaleOrderService.addSaleOrderItem, service mở một transaction và phát SELECT * FROM "SaleOrder" WHERE id = $1 FOR UPDATE trước khi đọc item rồi chèn/gộp. Dòng đơn đóng vai trò khoá điều phối cho TẤT CẢ thao tác ở cấp item trên đơn đó.

Cùng cách làm này áp dụng cho:

  • Cập nhật hàng loạt item (SaleOrderItemService.update)
  • Xoá sạch item (SaleOrderService.clearOrderItems)
  • Thanh toán (CheckoutService.checkout)
  • Gộp / tách

Hệ quả

ƯuNhược
Gộp không tranh chấp: item trùng luôn được gộp một cách xác địnhKhoá giữ tới khi transaction commit - thao tác chậm chặn người khác
Một cơ chế duy nhất cho mọi tranh chấp trên dòng đơnThao tác chỉ đọc không dùng chung khoá (chỉ thao tác ghi mới tuần tự hoá)
Tương thích với mô hình đồng thời của PostgreSQLThời gian chờ khoá hiện trên metrics DB - có thể gây cảnh báo nhầm
Không cần mutex ở cấp ứng dụngTransaction kéo dài gây chờ dây chuyền

Các phương án đã cân nhắc

Phương ánƯuNhượcLý do từ chối
Khoá lạc quan (cột version + thử lại)Khả chuyển giữa các DB, không phải chờBão thử lại khi nhiều thiết bị cùng thêm một item; logic phía client phức tạpSKU bán chạy ngoài thực tế khiến việc thử lại tốn kém
Khoá Redis ở cấp ứng dụngDùng được liên DBRủi ro khoá treo; điểm hỏng đơn lẻ; tốn đồng bộTệ hơn so với primitive sẵn có của DB
Unique index trên (orderId, itemType, itemId) + ON CONFLICT INCREMENTChỉ cần DBKhông chạy được với item CUSTOM (mỗi lần thêm là một dòng riêng); không ghi được giảm giá ở cấp dòngKhông phù hợp ngữ nghĩa mode=CUSTOM

Tham chiếu

  • sale/src/services/sale.service.ts (cách khoá trong addSaleOrderItem)
  • sale/src/services/sale-order-item.service.ts (khoá khi cập nhật hàng loạt)
  • core/src/models/schemas/sale/sale-item/schema.ts (trường mode)

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.