bulk_create()是Django批量插入首选方法,一次执行多条INSERT、跳过save()逻辑;需预设非空字段、指定batch_size、避免数据库函数;冲突处理需结合查询+bulk_update;应包裹在atomic中并分批处理以防内存溢出。

bulk_create() 是批量插入的首选方法
直接用 Model.objects.create() 循环插入几百条数据,会触发同等次数的 SQL INSERT 语句,性能极差。Django 提供的 bulk_create() 能把多个对象一次性拼成单条或多条 INSERT,大幅减少数据库往返。它不调用模型的 save() 方法,跳过信号、验证和默认值计算——这点必须清楚,否则容易漏掉字段或触发逻辑。
实操建议:
- 所有对象实例必须已设置好全部非空字段(
bulk_create()不执行full_clean()) - 显式传入
batch_size参数,比如batch_size=100,避免单次 SQL 过长导致 MySQL 报Packet too large - 主键为自增时,插入后对象实例的
id属性会被自动填充(仅限 PostgreSQL 和 SQLite;MySQL 需要ignore_conflicts=False且版本 ≥ 8.0.19 才可靠) - 不支持外键关联自动预加载,若需关联对象,先查出 ID 列表再赋值给外键字段
避免在 bulk_create() 中混用数据库函数
bulk_create() 只接受 Python 对象实例,不能把 F()、Now() 或 UUID() 这类数据库表达式直接塞进字段值里。例如写 created_at=Now() 会报 TypeError: Object of type Now is not JSON serializable —— 因为 ORM 底层是把对象序列化后拼 SQL 的。
正确做法是:
立即学习“Python免费学习笔记(深入)”;
- 用 Python 生成值:比如
created_at=timezone.now() - 若必须用数据库函数(如
DEFAULT触发触发器),改用原始 SQL 或connection.cursor().executemany() - 对 UUID 字段,别依赖数据库默认,用
uuid.uuid4()在 Python 层生成后赋值
update_fields 配合 get_or_create 实现“存在则更新”场景
单纯 bulk_create() 不处理冲突(如唯一键重复)。想实现“有则更新、无则插入”,不能靠 bulk_create(ignore_conflicts=True) 一招鲜——它只跳过冲突行,不更新已有记录。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
更可控的方式是分两步:
- 先用
Model.objects.filter(...).values_list('id', flat=True)查出已存在主键或唯一字段值 - 将待插入数据拆成「新增列表」和「待更新列表」
- 新增走
bulk_create();更新走bulk_update(),注意指定update_fields参数,否则可能误清空字段
例如更新邮箱和状态字段:bulk_update(objs, update_fields=['email', 'status']) —— 漏写 update_fields 会导致其他字段被设为 None。
注意事务边界和内存占用
bulk_create() 本身不开启事务,但通常应包裹在 transaction.atomic() 中,否则部分失败会导致数据不一致。更大的隐患是内存:一次塞几万对象进内存再传给 bulk_create(),Python 进程 RSS 会陡增,还可能触发 GC 停顿。
稳妥做法:
- 用生成器分批读取数据(比如从 CSV 或 API 流式解析),每 500–1000 条调用一次
bulk_create() - 避免把整个文件读进
list再处理 - PostgreSQL 用户可考虑
django-postgres-extra的on_conflict(),但引入第三方库前先确认是否真需要 Upsert 语义
真正卡住性能的往往不是 ORM 写法,而是没控制好批次大小、没绕开信号和验证、或者把数据库函数错当 Python 值用。这些点不手动验证一遍,光看文档容易掉坑里。


















