bulk_create 比 for 循环快因合并多条 INSERT 为单条 SQL,减少数据库往返与事务开销;但不触发 save 相关信号、不自动填充 auto_now/add 字段及 default 函数,需手动设置所有字段值。

bulk_create 为什么比 for 循环快得多
因为 bulk_create 把多条 INSERT 合并成一条 SQL 语句发给数据库,而 for 循环每次 save() 都触发一次独立的 INSERT + 可能的事务开销。实测导入 10 万条数据,bulk_create 通常只要 2–5 秒,for 循环可能卡在 2 分钟以上,还容易触发连接超时。
但要注意:它不调用模型的 save() 方法,所以 pre_save/post_save 信号不会触发,auto_now_add、auto_now 字段也不会自动填充,default 函数(如 default=uuid4)也不会执行。
- 必须手动设置所有字段值,包括
id(如果没设primary_key=False)、created_at、updated_at等 - 如果依赖
default或auto_now_add,得自己补上:created_at=datetime.now() - 外键字段要传实际对象实例,不能传 ID 数字(除非用
bulk_create(..., ignore_conflicts=True)并配合数据库约束)
如何避免 MemoryError 和数据库超时
bulk_create 一次性塞太多对象进内存或发太长 SQL,会直接崩——尤其在低配服务器或 PostgreSQL 上。Django 默认不限制批次大小,得自己切片。
- 按每 1000–5000 条一批较稳妥(MySQL 可稍大,PostgreSQL 建议 ≤2000)
- 用
itertools.islice或列表切片分批,别一次性生成全部对象再切 - 加
batch_size参数:MyModel.objects.bulk_create(objs, batch_size=2000) - 如果表有唯一索引且数据可能重复,加上
ignore_conflicts=True(仅 Django 2.2+,且需数据库支持,如 PostgreSQL 9.5+/MySQL 8.0.19+)
示例:
立即学习“Python免费学习笔记(深入)”;
from itertools import islice
def chunked_bulk_create(queryset, model, batch_size=2000):
iterator = iter(queryset)
while True:
batch = list(islice(iterator, batch_size))
if not batch:
break
model.objects.bulk_create(batch, batch_size=batch_size)
bulk_create 报错 “null value in column X violates not-null constraint” 怎么办
这是最常踩的坑:字段明明在模型里写了 blank=True, null=True,但数据库迁移后实际字段仍是 NOT NULL,或者忘了设默认值又没传值。
- 先查数据库真实 schema:
\d myapp_mymodel(PostgreSQL)或DESCRIBE myapp_mymodel;(MySQL) - 确认
CharField类型字段没设default="",空字符串和None在 NOT NULL 下都非法 -
DateTimeField必须显式传datetime对象,不能留None(即使模型允许 null) - 外键字段传了
None?确保对应字段定义是ForeignKey(..., null=True)且数据库列确实是NULL
想跳过重复数据又不想报错,该用 ignore_conflicts 还是 get_or_create
ignore_conflicts=True 是纯数据库层去重,快但粗暴——冲突行直接丢弃,不返回、不通知、不触发任何逻辑。而 get_or_create 是 Python 层查+存,单条慢,批量写根本不能用。
- 只对有数据库唯一约束(
UniqueConstraint或unique=True字段)的场景有效 - PostgreSQL 支持
ON CONFLICT DO NOTHING,MySQL 8.0.19+ 支持ON DUPLICATE KEY UPDATE,但ignore_conflicts不做 update,只跳过 - 如果需要记录哪些被跳过、或冲突时更新部分字段,别硬扛——改用原生 SQL 或
INSERT ... ON CONFLICT DO UPDATE(PostgreSQL)
真正麻烦的是:Django 的 ignore_conflicts 不返回插入结果数量,也没法知道哪几条被跳了。这事得靠日志或前置去重兜底。


















