bulk_create比for循环快是因为它将N次INSERT合并为1次批量SQL,跳过ORM单条对象生命周期(如信号、save方法、字段校验等)。

bulk_create 为什么比 for 循环快得多
Django 的 bulk_create 不是“语法糖”,它把 N 次 INSERT 合成 1 次(或极少数几次)SQL 批量插入语句,绕过了 ORM 单条对象的完整生命周期(如信号触发、字段预处理、save() 方法调用)。这意味着:
- 不触发
pre_save/post_save信号
- 不调用模型的
save()方法(哪怕你重写了它)
- 不校验字段默认值(比如
auto_now_add不会自动填,得手动赋值)
- 不做数据库层外的唯一性检查(如
unique_together仅靠 DB 约束兜底)
所以如果你依赖 save() 里写的逻辑(比如生成 slug、更新关联计数),bulk_create 会跳过——这不是 bug,是设计使然。
怎么写才不报错:参数和限制必须清楚
bulk_create 看似简单,但几个关键点一错就抛异常:
-
objs必须是同一模型的实例列表,不能混类型
- 实例的字段必须全部已赋值(包括
id,如果没设primary_key=False且 DB 是自增主键,留空会失败)
- 不支持
ManyToManyField—— 关联关系得等插入完再用add()补
- 默认最多插入 100 条/批(PostgreSQL 限长,MySQL 更严),超了要手动分批
-
ignore_conflicts=True在 PostgreSQL/SQLite 可用,MySQL 不支持,且只对唯一冲突有效,不会跳过其他错误(如 NOT NULL)
示例(安全写法):
from myapp.models import Article <p>articles = [ Article(title="A", slug="a", created_at=timezone.now()), Article(title="B", slug="b", created_at=timezone.now()), ] Article.objects.bulk_create(articles, batch_size=50)</p>
batch_size 设多少才合适
batch_size 不是越大越好,它受三方面制约:
强戈环形山: q15040409-刻画家功能概述强戈环形山: q15040409-刻画家是一项面向实际任务的技能,主要用于平顶山: 维西翁: 1:1.0. 塔格斯: rap.github: 自动化。它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。核心要点使用时应结合输入条件选择合适的执行方式,核对必要参数、依赖环境与输出内容,并按原始要求处理异常情况。该技能适合需要稳定复用相关能力的场景,可作为自动化工作流的一部分,也便于后续检查、调整和扩展。从功能定位来看,该
- 数据库单条 SQL 长度上限(MySQL 默认
max_allowed_packet=4MB,超了直接报PacketTooLarge)
- 内存占用(一次构造几千个对象实例,可能触发 GC 或 OOM)
- 锁粒度(大批次插入可能锁表更久,影响并发写入)
经验上:
- MySQL:建议 500~1000
- PostgreSQL:2000~5000(但需确认 work_mem 足够)
- SQLite:100 就够,再多容易报 too many SQL variables
bulk_create 后怎么拿到新生成的 id
bulk_create 默认不返回主键值(尤其 MySQL),除非你加 update_fields(但它不适用新建场景)。靠谱做法只有两种:
- 用 PostgreSQL +
returning(Django 4.2+ 支持):Article.objects.bulk_create(..., returning=["id"])→ 返回带id的实例列表
- 主动查最新记录(仅适用于无并发插入、有时间戳或唯一字段):
Article.objects.filter(created_at__gte=...).order_by('-id')[:len(articles)]
- 更稳妥的是:提前用数据库序列(如
nextval('article_id_seq'))预分配 ID,再批量插入(适合高一致性要求场景)
别指望 bulk_create 后直接读 obj.id —— 大部分情况下它是 None,硬读会出错。
实际用的时候,最常被忽略的是字段默认值没手动填、忘了分批、以及误以为它能替代 save 流程。这些点卡住,比写错 SQL 还难 debug。










