最近重操 CRUD 旧业,又有一些新的发现,故增加一篇 Django ORM:天使与魔鬼 Part II。
利用 batch_size 控制数据库单次提交的大小
bulk_create
和 bulk_update
是我们常用的批量创建、更新的方法,但批量提速一时爽,提交过长会直接导致任务失败。
之前没有细致查阅文档,想当然 手写了批量提交分片的逻辑 ,虽然也完全实现了功能,但终究多了一份需要维护的逻辑,实际上直接用 Django 默认提供的 batch_size
即可。
from itertools import islice
batch_size = 100
objs = (Entry(headline='Test %s' % i) for i in range(1000))
while True:
batch = list(islice(objs, batch_size))
if not batch:
break
Entry.objects.bulk_create(batch, batch_size)
通过 Prefetch 控制预取的查询
N 1 问题是非常常见的查询效率杀手。在 Django 中我们通常会使用 selected_related
或prefetch_related
来预取关联对象,来减少和 DB 之间的交互,但是在使用上也需要有一些注意的地方。
首先,预取需要精确控制到字段。
Django 默认的查询方式都是粗放的,例如普通查询不使用 values
或者 only
时都是 select *
,而预取也不例外,看看下面这个例子。
class Foo(models.Model):
...
class Bar(models.Model):
foo = models.ForeignKey(Foo)
...
class Baz(models.Model):
"""A very large table"""
foo = models.ForeignKey(Foo)
我们在查询 Foo
时,会尝试预取关联字段以加速后续数据读取,但如果我们在调用时不加任何参数:Foo.objects.all().prefetch_related()
,默认地 Django 会将所有关联字段都取出来,加入 Baz
表无比巨大,本来用作性能优化的 prefetch_related
就会摇身变成耗时怪兽。
此外,我们还会遇到级联预取的场景。
代码语言:javascript复制class Foo(models.Model):
...
class Bar(models.Model):
foo = models.ForeignKey(Foo, related_name="bars")
...
class Baz(models.Model):
bar = models.ForeignKey(Bar, related_name="bazs")
large_config = models.JSONField()
...
此时在后续的循环处理中,我们需要通过 Foo
对象查询到 Baz
的数据,为了避免 N 1 我们也会多级预取:
Foo.objects.filter().select_related("bars").prefetch_related("bars__bazs")
此时二级预取也是默认获取全部字段,倘若 Baz
表中有一个需要额外耗时序列化的字段,同样会使优化适得其反。这时可以考虑引入 Prefetch
对象,做更细致的查询控制。
Foo.objects.filter().select_related("bars")
.prefetch_related(
Prefetch("bars__bazs", queryset=Baz.objects.defer("large_config"))
)
是不是觉得 ORM 查询本身也挺繁杂的?用 SQL 有时会更直接清晰地多。所以也会有一些完全不使用 ORM 的观点。在我看来,ORM 能让 90% 的查询都变得结构化更清晰、更易维护、甚至更安全,但剩下的 10% 也许会耗费更多的精力,所以何时使用 ORM 是根据具体项目场景来定的,不能因噎废食。
小广告
是不是觉得 Part II 内容有点少?没关系,更多的内容我都放在了这里。
GitHub - TencentBlueKing/python-best-practices
Contribute to TencentBlueKing/python-best-practices development by creating an account on GitHub.
https://github.com/TencentBlueKing/python-best-practices
我和团队小伙伴整理了很多 PythonDjangoDRF 的最佳实践经验,项目会持续更新,欢迎一起探讨维护,希望每一个 CRUD 男孩/女孩都能少踩坑。