zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Django ORM:天使与魔鬼 II

django II ORM 天使 魔鬼
2023-06-13 09:14:27 时间

最近重操 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 内容有点少?没关系,更多的内容我都放在了这里。

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

我和团队小伙伴整理了很多 Python\Django\DRF 的最佳实践经验,项目会持续更新,欢迎一起探讨维护,希望每一个 CRUD 男孩/女孩都能少踩坑。