bulk_create比for循环save快得多是因为它将n次insert合并为1条(或极少数)sql语句,跳过orm单条生命周期(不触发信号、不调用save、不自动填充auto_now字段等),实测1000条数据耗时从~2.5秒降至~0.18秒。

bulk_create 为什么比 for 循环 save() 快得多
因为它把 N 次 INSERT 合并成 1 条(或极少数几条)SQL 语句,跳过了 ORM 单条对象的完整生命周期。这意味着:
- 不触发
pre_save/post_save信号 - 不调用模型的
save()方法(哪怕你重写了它) - 不校验字段默认值(比如
auto_now_add不会自动填) - 不做数据库层外的唯一性检查(
unique_together仅靠 DB 约束兜底)
这些“跳过”不是 bug,是设计使然——换来的是数量级的性能提升。1000 条插入,for 循环通常耗时 ~2.5 秒;bulk_create 控制得当,能压到 ~0.18 秒。
不设 batch_size 就直接 bulk_create([]) 会崩
不传 batch_size 参数时,Django 会把全部实例拼进一条 INSERT INTO ... VALUES (),(),()... 语句。这在 MySQL 和 SQLite 上极易触发底层限制:
- MySQL 默认
max_allowed_packet=4MB,1000 条含文本字段的记录轻松超限,报错如:django.db.utils.OperationalError: (2006, "MySQL server has gone away") - SQLite 对单条 SQL 长度有隐式上限,超长直接抛
sqlite3.OperationalError - 内存占用随数据量线性增长,10 万条可能吃掉几百 MB,生产环境容易 OOM
经验上,batch_size=500–1000 是安全起点;文本多或含 TextField/BinaryField 时建议降到 100–200;上线前务必用真实字段结构压测。
bulk_create 后拿不到主键 ID 怎么办
bulk_create 在 MySQL/SQLite 下完全不返回主键(book.id 是 None),PostgreSQL 仅在未设 batch_size 时通过 RETURNING 返回。所以这种链式写法必然失败:
books = Book.objects.bulk_create(book_instances) chapters = [Chapter(book_id=b.id, title='') for b in books] # b.id 全为 None Chapter.objects.bulk_create(chapters)
可行解只有两个:
- 先插主表 +
ignore_conflicts=True(确保唯一约束存在),再用Book.objects.filter(title__in=[...]).values_list('id', 'title')反查 ID,靠业务字段对齐 - 手动预分配 ID:清空表后显式设
id字段(如Book(id=i+1, title=...)),适用于初始化、迁移等可控场景
别指望靠 refresh_from_db() 补 ID——它对 bulk_create 无效。
auto_now/auto_now_add 字段为空、信号不触发,怎么补
bulk_create 完全绕过 Model.save(),所以:
-
auto_now和auto_now_add字段不会自动填充,必须显式赋值:created_at=timezone.now() - 依赖
save()里写的逻辑(比如生成 slug、更新关联计数)会被跳过
这不是疏漏,是明确的设计取舍。如果你需要这些行为,要么拆成小批量 + 单条 save(),要么把逻辑提前到构造实例时做。
真正容易被忽略的是:不同数据库对 batch_size 的容忍度差异极大,MySQL 和 SQLite 尤其敏感;而字段类型(尤其是大文本)对安全阈值的影响,远比行数更关键。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











