Loop instance.save() cho 10k row = 10k INSERT round-trip → tốn vài chục giây và ép DB ngộp connection. Bulk operations gom thành 1-N câu SQL, nhanh hơn 10–100 lần.
Model.objects.bulk_create(objs, batch_size=1000) chạy 1 INSERT cho mỗi batch, có ignore_conflicts=True (skip duplicate) và update_conflicts=True (upsert kiểu Postgres). Model.objects.bulk_update(objs, fields, batch_size=1000) chạy 1 UPDATE ... CASE WHEN cho mỗi batch — object phải có sẵn pk. QuerySet.update(**kwargs) chạy 1 UPDATE SQL cho mọi row match filter, không cần load object về Python.
# Sai: 10k query
for row in rows:
Item(sku=row.sku, price=row.price).save()
# Đúng: 10 query (batch_size=1000)
Item.objects.bulk_create(
[Item(sku=r.sku, price=r.price) for r in rows],
batch_size=1000,
)
# Cập nhật cùng giá trị, không cần load object
Item.objects.filter(category='archived').update(visible=False)Bulk operation bỏ qua signal và bỏ qua Model.save() override — nếu logic phụ thuộc signal (audit log, invalidate cache) thì phải tự handle sau bulk. auto_now cũng không chạy với update(); truyền updated_at=timezone.now() thẳng vào kwargs khi cần.
Looping instance.save() for 10k rows = 10k round-trip INSERTs → tens of seconds wasted and the DB drowns in connections. Bulk operations collapse into 1-N SQL statements → 10–100× faster.
Model.objects.bulk_create(objs, batch_size=1000)— oneINSERTper batch. Bypassessave()overrides,pre_save/post_savesignals,auto_now. Supportsignore_conflicts=True(skip duplicates) andupdate_conflicts=True(Postgres-style upsert).Model.objects.bulk_update(objs, fields, batch_size=1000)— oneUPDATE ... CASE WHENper batch. Objects must already have apk.QuerySet.update(kwargs)** — oneUPDATESQL for every matching row; same value or anF()expression. No need to load objects into Python.
# Wrong: 10k queries
for row in rows:
Item(sku=row.sku, price=row.price).save()
# Right: 10 queries (batch_size=1000)
Item.objects.bulk_create(
[Item(sku=r.sku, price=r.price) for r in rows],
batch_size=1000,
)
# Same value across rows, no need to load objects
Item.objects.filter(category='archived').update(visible=False)Pitfall: Bulk operations skip signals and skip Model.save() overrides — if your logic relies on signals (audit log, cache invalidation), handle it manually after the bulk. auto_now also does not fire with update(); pass updated_at=timezone.now() explicitly when needed.