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