select_for_update() khoá dòng cho tới khi transaction kết thúc, nên bắt buộc nằm trong transaction.atomic() — ngoài atomic sẽ ném TransactionManagementError vì autocommit khiến khoá nhả ngay lập tức.
Ba tùy chọn hay dùng:
with transaction.atomic():
# wait for the lock (default): risk of a long queue under load
seat = Seat.objects.select_for_update().get(pk=pk)
# fail fast instead of waiting
seat = Seat.objects.select_for_update(nowait=True).get(pk=pk) # raises DatabaseError
# queue-worker pattern: skip rows another worker already holds
jobs = Job.objects.select_for_update(skip_locked=True).filter(status='pending')[:10]nowait=True— không chờ, ném lỗi ngay. Dùng khi thà báo "hệ thống bận" còn hơn để request treo.skip_locked=True— bỏ qua dòng đang bị khoá. Đây là cách chuẩn để nhiều worker cùng rút việc từ một bảng mà không giẫm lên nhau.of=('self',)— khi query cóselect_related, mặc định database khoá tất cả bảng được join.ofgiới hạn phạm vi khoá lại đúng bảng cần.
Vấn đề ở tải cao:
- Deadlock khi hai transaction khoá cùng tập dòng theo thứ tự ngược nhau. Phòng bằng cách luôn khoá theo thứ tự cố định (vd order_by('pk')) trong mọi code path.
- Lock contention: giữ khoá suốt thời gian gọi API ngoài là cách nhanh nhất để cạn connection pool. Khoá càng muộn, nhả càng sớm; việc I/O bên ngoài đẩy ra ngoài block.
- Với PostgreSQL, select_for_update không dùng được với LEFT OUTER JOIN (quan hệ nullable) — phải chỉ định of hoặc bỏ select_related.
Khi nào không cần khoá: nếu chỉ tăng/giảm một số, F() expression (F('stock') - 1) đã atomic ở tầng SQL, rẻ hơn nhiều so với khoá dòng.