Vì post_save bắn ngay sau khi save() chạy xong, nhưng transaction chưa commit. Worker Celery là tiến trình khác, dùng connection khác, nên nó SELECT vào lúc dữ liệu còn nằm trong transaction chưa commit — kết quả là DoesNotExist, hoặc tệ hơn là đọc được bản cũ.
Rõ nhất khi bật ATOMIC_REQUESTS hoặc bọc view trong transaction.atomic(): mọi save() nằm trong một transaction kéo dài đến hết request.
Cách xử lý: hoãn side effect đến sau commit bằng transaction.on_commit().
from django.db import transaction
@receiver(post_save, sender=Order)
def on_order_created(sender, instance, created, **kwargs):
if not created:
return
transaction.on_commit(lambda: send_invoice.delay(instance.pk))Callback chỉ chạy khi transaction ngoài cùng commit thành công; transaction rollback thì callback bị bỏ luôn — đúng ý muốn, vì không nên gửi mail cho đơn hàng đã rollback.
Áp dụng chung: mọi side effect ra khỏi database (gửi mail, gọi webhook, đẩy queue, xoá cache) đều nên đặt trong on_commit, không riêng Celery. Trong test dùng TestCase, transaction không bao giờ commit nên callback không chạy — dùng captureOnCommitCallbacks() để kiểm tra.