bulk_create 快是共识,但它「省掉了什么」「不能做什么」常被忽略:信号、pk、冲突、继承。bulk_update 又到底快多少?这篇在 5000 条数据上逐项验证。
循环 Item(...).save() 查询 5000 次 1776.8 ms 表里 5000 行 pre_save/post_save 信号 5000/5000 bulk_create(默认) 查询 1 次 66.5 ms 表里 5000 行 pre_save/post_save 信号 0/0 bulk_create(batch_size=1000) 查询 7 次 60.3 ms 表里 5000 行 pre_save/post_save 信号 0/0
查询数和耗时都差一个数量级(本机 SQLite,没有网络往返;换成远程数据库,差别只会更大)。信号 0 次——所以依赖 post_save 的逻辑不会执行。
返回的对象 pk = [15001, 15002] created 字段(auto_now_add)= ['2026-09-30 12:12:41', '2026-09-30 12:12:41']
SQLite 3.35+ 与 PostgreSQL 支持 RETURNING,所以能回填 pk;其它数据库不保证(本文没有测)。字段自己的 pre_save(比如 auto_now_add)仍会执行。
bulk_update(5000 个对象, ["n"])(默认):查询 3 次,494 ms
第一条 SQL 前 200 字符: BEGIN
不同 batch_size 下更新 5000 个对象(每种取 3 次中最快;查询数含 BEGIN / COMMIT):
batch_size=100 查询 52 次 275 ms
batch_size=500 查询 12 次 279 ms
batch_size=1000 查询 7 次 288 ms
batch_size=5000 查询 3 次 361 ms
逐个 save(update_fields=["n"]):查询 5000 次,370 ms
信号: {'pre': 5000, 'post': 5000}用 CASE WHEN 一次更新多行。batch_size 控制每条 UPDATE 涵盖多少对象,同时决定查询数。这里量到的耗时并没有随查询数同比下降:SQLite 在本机没有网络往返,一条巨大的 CASE WHEN 本身有代价。这个结论只适用于这台机器的 SQLite,不代表远程数据库。
默认(遇到重复): IntegrityError: UNIQUE constraint failed: lab_uniq.code
ignore_conflicts=True:表里 = [('a', 1), ('b', 2)] 返回对象的 pk = [None, None]
update_conflicts=True:表里 = [('a', 100), ('b', 2), ('c', 3)]ignore_conflicts 会让 pk 不再被回填(全是 None);update_conflicts 要同时给 unique_fields 和 update_fields。
多表继承的子类 bulk_create → ValueError: Can't bulk create a multi-table inherited model bulk_create 触发的信号(pre/post): 0 0 对比一次 save(): 1 1
多表继承的子类、多对多字段(本文没有测)都不能靠 bulk_create 一次写完;绕过 save() 意味着自定义 save() 里的逻辑也不会运行。