Cần cập nhật cùng một giá trị cho hàng trăm, hàng nghìn bản ghi? Đừng lặp qua từng object rồi gọi save() — dùng QuerySet.update() để Django gom tất cả thành đúng một câu SQL UPDATE. Cách này nhanh hơn hẳn, tránh race condition khi kết hợp với F(), và hợp nhất cho tác vụ hàng loạt như đổi trạng thái hay trừ tồn kho.

Vấn đề

Giả sử bạn cần xuất bản 1.000 bài nháp. Cách nhiều người viết đầu tiên là vòng lặp:

for article in Article.objects.filter(status="draft"):
article.status = "published"
article.save()

Code này chạy đúng nhưng rất phí: mỗi vòng lặp tốn một câu SELECT để load object rồi một câu UPDATE để lưu — 1.000 bài là 2.000 query, lại giữ 1.000 object trong RAM. Tệ hơn, giữa lúc load object và gọi save() có thể có request khác sửa cùng bản ghi — save() sẽ ghi đè lặng lẽ. Lỗi này chỉ lộ ra khi traffic tăng, debug rất mệt.

Giải pháp

Một dòng thay cả vòng lặp:

updated = Article.objects.filter(status="draft").update(status="published")
print(updated) # số dòng đã cập nhật, ví dụ: 1000

update() chạy đúng một câu UPDATE, không load object nào vào RAM, và trả về số dòng bị ảnh hưởng — tiện để log hay kiểm tra kết quả. Đổi nhiều cột cùng lúc thì truyền thêm tham số:

from django.utils import timezone
Article.objects.filter(status="draft").update(
status="published",
published_at=timezone.now(),
)

Trừ tồn kho là trường hợp kinh điển thứ hai. Đừng đọc stock về Python rồi trừ và lưu lại — dùng F() để phép tính diễn ra ngay trong SQL, nguyên tử ở tầng database:

from django.db.models import F
Product.objects.filter(id=42).update(stock=F("stock") - 1)

Dù 10 request trừ kho cùng lúc, mỗi lần trừ đều tính trên giá trị mới nhất trong database, không mất mát. Lưu ý: update() chỉ cập nhật cột trong bảng chính, không cập nhật được field của model liên quan — nhưng vẫn lọc được qua quan hệ, ví dụ Entry.objects.filter(blog__id=1).update(comments_on=True).

Áp dụng vào dự án của bạn

Lần tới khi viết management command dọn dữ liệu, cron job đổi trạng thái đơn hàng hay API endpoint “đánh dấu tất cả đã đọc”, hãy kiểm tra xem mình có đang lặp save() không. Nếu mọi bản ghi nhận cùng một giá trị, update() gần như luôn là đáp án đúng — ít query, nhanh hơn, an toàn hơn khi nhiều request cùng chạy. Chỉ nhớ hai giới hạn: update() không gọi save() nên không phát signal, và auto_now cũng không tự đổi — nếu cần, hãy set thời gian rõ ràng trong câu lệnh.

Bài viết liên quan

Câu hỏi thường gặp

update() có gọi save() hay phát signal không?

Không. update() cập nhật thẳng ở tầng SQL nên không gọi save(), không phát pre_save/post_save, không chạy full_clean(). Model nào có logic trong save() hay signal thì phải lặp và gọi save() từng object.

update() khác bulk_update() ở điểm nào?

update() gán cùng một giá trị cho mọi dòng khớp filter(), chỉ một câu SQL. bulk_update() gán giá trị khác nhau cho từng object bạn đưa vào — dùng khi mỗi bản ghi cần một giá trị riêng.

Có cập nhật được field của model liên quan không?

Không. update() chỉ chạm tới các cột trong bảng chính, ví dụ Entry.objects.update(blog__name="foo") sẽ lỗi. Muốn đổi dữ liệu model liên quan, hãy cập nhật trực tiếp trên queryset của model đó.

Trường auto_now có tự đổi khi dùng update() không?

Không. Vì update() bỏ qua save() nên trường auto_now giữ nguyên giá trị cũ. Muốn cập nhật cả thời gian, hãy truyền tường minh, ví dụ update(status="published", updated_at=timezone.now()).

Khi nào vẫn nên dùng vòng lặp save()?

Khi mỗi object cần logic riêng trong save(), khi cần signal để đồng bộ cache hay gửi thông báo, hoặc cần validate trước khi lưu. Còn lại, cập nhật hàng loạt cùng một giá trị thì update() luôn nhanh hơn.

💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.

Nguồn tham khảo