bigywater.tech
Django · 110

Signal 与 Bulk 操作的关系

想用信号监听「所有写入」的人,常常在 bulk 操作那里踩空:批量写入不会触发保存信号,而删除又恰恰相反——一旦有人监听了 delete 信号,批量删除会变慢很多。这篇把这两头都量出来。

没有信号
bulk_create / bulk_update / queryset.update() 都不发 pre_save、post_save
3 → 23 条 SQL
删 2000 行:没有 delete 信号 → 有 post_delete receiver
0.6 → 125.9 ms
删 2 万行的耗时,同样的对比

写入:哪些发信号

实测 · 本机执行输出
save() × 100(逐个)                   信号触发 {'pre_save': 100, 'post_save': 100}  SQL 语句数 100
bulk_create(100 行)                 信号触发 无  SQL 语句数 1
bulk_update(100 行)                 信号触发 无  SQL 语句数 3
queryset.update()                  信号触发 无  SQL 语句数 1

只有走 save() 的写入才会发 pre_save / post_save。如果业务上「所有的写入都要触发某个动作」,批量写入需要另外处理(比如写完之后自己调用同样的函数)。

删除:反过来

实测 · 本机执行输出
delete() 2000 行,没有 delete 信号       信号触发 无  SQL 语句数 3
delete() 2000 行,有 post_delete receiver 信号触发 {'post_delete': 2000}  SQL 语句数 23

没有 delete 信号且没有需要级联处理的关系时,queryset.delete() 走快速删除(一条 DELETE)。只要模型上挂了 pre_delete / post_delete receiver,Django 就得把对象取出来逐个发信号。

耗时差多少

实测 · 本机执行输出
  没有 delete 信号      0.6 ms
  有 post_delete       125.9 ms

0.6 ms 这么快,可能有 SQLite 对无条件 DELETE 的优化(我没有验证),所以「200 倍」只适合这个测法,不要当作通用倍数;但方向是确定的:监听 delete 信号会让批量删除退化为逐行处理。

手动补发

实测 · 本机执行输出
遍历 5 行手动 post_save.send            信号触发 {'post_save': 5}  SQL 语句数 1

分步演示

bulk 操作与信号
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite;Item 模型监听 pre_save / post_save,Plain 模型(无外键)用来测 delete
    • 没有验证:有级联外键时的删除路径、PostgreSQL / MySQL 上的 SQL 条数与耗时、bulk_create 的 ignore_conflicts / update_conflicts、Django 对 SQLite 无条件 DELETE 的优化细节
    京ICP备2026067502号-1京公网安备11010502063487号