👼 Django ORM:天使与魔鬼 II

date
Sep 9, 2022
slug
django-orm-best-practice-II.html
status
Published
tags
python
django
orm
best-practice
tech
summary
CRUD boy 回来了
type
Post
最近重操 CRUD 旧业,又有一些新的发现,故增加一篇 Django ORM:天使与魔鬼 Part II。

利用 batch_size 控制数据库单次提交的大小

bulk_createbulk_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_relatedprefetch_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 就会摇身变成耗时怪兽。
此外,我们还会遇到级联预取的场景。
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 内容有点少?没关系,更多的内容我都放在了这里。
我和团队小伙伴整理了很多 Python\Django\DRF 的最佳实践经验,项目会持续更新,欢迎一起探讨维护,希望每一个 CRUD 男孩/女孩都能少踩坑。

© bluesyu 2019 - 2023

powered by nobelium