bulk_create可将批量写入提速至百毫秒级,但存在不返回主键、不触发信号、不校验字段、batch_size不当致内存溢出或sql报错等问题;需合理设batch_size(500–1000)、预分配id或反查id、显式赋值auto_now字段,并慎用ignore_conflicts与update_conflicts。

bulk_create 能把批量写入速度从秒级拉到百毫秒级,但直接替换 for item in data: Model.objects.create(...) 往往会踩坑——它不返回主键、不触发信号、不校验字段、还可能因 batch_size 不当导致内存炸掉或 SQL 报错。
batch_size 不设等于自毁:MySQL/SQLite 有硬限制
不传 batch_size 参数时,Django 会把全部实例拼进一条 INSERT INTO ... VALUES (),(),()... 语句。MySQL 默认 max_allowed_packet=4MB,SQLite 对单条语句长度也有隐式上限。1000 条带文本字段的记录很容易超限,报错类似:
django.db.utils.OperationalError: (2006, "MySQL server has gone away")
合理值取决于字段复杂度和数据库配置,经验区间是 500–1000;文本多或含二进制字段时建议降到 100–200。
- 用
len(str(instance.__dict__))粗略估算单条序列化后大小 - 生产环境务必压测不同
batch_size下的内存占用与耗时 - PostgreSQL 相对宽松,但依然建议分批,避免长事务阻塞
关联表写入失败:bulk_create 不返回主键
这是最常卡住人的点:bulk_create 在 MySQL 和 SQLite 下完全不返回插入后的 id,而 PostgreSQL 仅在未指定 batch_size 时才通过 RETURNING 返回(且仅限主键)。所以这种写法必然失败:
books = Book.objects.bulk_create([...]) # ↓ books[0].id 是 None(MySQL/SQLite)或未设置(部分 PG 场景) chapters = [Chapter(book_id=book.id, title='...') for book in books] Chapter.objects.bulk_create(chapters) # book_id 全为 None
解决路径只有两条:
- 先用
bulk_create(..., ignore_conflicts=True)插入主表,再用Book.objects.values_list('id', 'title')反查 ID(适合已有唯一约束字段可定位) - 手动预分配 ID:清空表 + 显式设
id字段 +bulk_create(仅适用于初始化、迁移等可控场景)
绕过模型逻辑:save() 的副作用全没了
bulk_create 完全跳过 Model.save() 方法,意味着:
-
pre_save/post_save信号不会触发 -
auto_now/auto_now_add字段不会自动填充(必须显式赋值) - 自定义的
clean()、full_clean()校验不执行 - 外键字段若传了对象而非 ID(如
author=Author.objects.get(...)),会抛ValueError
如果业务强依赖这些逻辑,别硬套 bulk_create——要么提前做数据清洗(比如用 datetime.now() 填 created_at),要么拆成两步:小批量 create() + 异步补处理。
ignore_conflicts 和 update_conflicts 不是万能胶
ignore_conflicts=True 只在支持 ON CONFLICT DO NOTHING(PostgreSQL)或 INSERT IGNORE(MySQL)的数据库生效;SQLite 需要 3.24+ 且开启 WAL 模式。它只忽略唯一约束/主键冲突,**不处理检查约束、外键约束失败**,后者仍会抛异常。
update_conflicts(Django 4.1+)更危险:它本质是 INSERT ... ON CONFLICT DO UPDATE,但更新字段必须显式声明,漏写就静默丢数据。例如:
Book.objects.bulk_create(
[Book(isbn='123', title='A', price=10)],
update_conflicts=True,
update_fields=['title'], # ← price 不在此列,冲突时 price 不会更新
unique_fields=['isbn']
)
实际中,除非你明确控制冲突行为且数据库版本达标,否则优先用 get_or_create 或原生 INSERT ... ON DUPLICATE KEY UPDATE 更稳妥。
真正难的不是调用 bulk_create,而是判断哪些字段必须预计算、哪些约束必须提前兜底、哪些关联必须拆解重排——这些细节不处理,性能提升就只是幻觉。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











