bigywater.tech
Django · 36

bulk_create() 与 bulk_update() 的限制

bulk_create 快是共识,但它「省掉了什么」「不能做什么」常被忽略:信号、pk、冲突、继承。bulk_update 又到底快多少?这篇在 5000 条数据上逐项验证。

5000 → 1
5000 条:循环 save() → bulk_create 的查询次数
0 次信号
bulk_create 不触发 pre_save / post_save;循环 save() 触发 5000 次
bulk_update
本机 SQLite:不同 batch_size 361~275 ms,逐个 save 370 ms——差别没有查询数那么大

bulk_create vs 循环 save()

实测 · 本机执行输出
循环 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 与自动字段

实测 · 本机执行输出
返回的对象 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

实测 · 本机执行输出
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() 里的逻辑也不会运行。

分步演示:bulk 系列

bulk_create / bulk_update
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite,5000 条;查询数含 BEGIN / COMMIT
    • 没有验证:PostgreSQL / MySQL 上的耗时与 bulk_update 的 SQL、pk 回填在 MySQL 上是否可用、多对多批量写入(through 表 bulk_create)、超大批量时 SQLite 变量个数上限触发的自动分批边界
    京ICP备2026067502号-1京公网安备11010502063487号