Django的数据库层提供了各种方法来帮助开发人员充分利用他们的数据库。此文档收集到相关文档的链接,并添加各种提示,这些提示组织在多个标题下,概述了在尝试优化数据库使用时要采取的步骤。
作为一般的编程实践,这是不言而喻的。找出 what queries you are doing and what they are costing you . 使用 QuerySet.explain()
了解具体情况 QuerySet
s由数据库执行。您也可以使用外部项目,例如 django-debug-toolbar, 或者直接监视数据库的工具。
请记住,根据您的需求,您可能正在优化速度或内存或两者。有时为一个优化会对另一个不利,但有时它们会互相帮助。另外,由数据库进程完成的工作可能与在Python进程中完成的工作量不同(对您来说)。这取决于您决定您的优先级是什么,平衡在哪里,并根据需要对所有这些进行分析,因为这将取决于您的应用程序和服务器。
对于接下来的所有内容,请记住在每次更改之后进行概要分析,以确保更改是一个好处,并且考虑到代码可读性的降低,这是一个足够大的好处。 All 在下面的建议中,有一个警告是,在您的情况下,一般原则可能不适用,甚至可能被推翻。
……包括:
索引。这是第一要务, 之后 您已经根据分析确定了应该添加哪些索引。使用 Meta.indexes
或 Field.db_index
从Django添加这些。考虑将索引添加到经常使用 filter()
, exclude()
, order_by()
等,因为索引可能有助于加速查找。请注意,确定最佳索引是一个复杂的依赖于数据库的主题,这将取决于您的特定应用程序。维护索引的开销可能超过查询速度的任何提高。
适当使用字段类型。
我们假设你已经做了上面列出的事情。本文档的其余部分重点介绍如何使用Django,使您不必做不必要的工作。本文档也不涉及适用于所有昂贵操作的其他优化技术,例如 general purpose caching .
QuerySet
的S¶理解 QuerySets 对于使用简单的代码获得良好的性能至关重要。特别地:
QuerySet
评价¶为了避免性能问题,必须了解:
那个 QuerySets are lazy .
什么时候? they are evaluated .
以及整个数据库的缓存 QuerySet
中,存在对ORM对象上的属性结果的缓存。通常,不可调用的属性将被缓存。例如,假设 example blog models :
>>> entry = Entry.objects.get(id=1)
>>> entry.blog # Blog object is retrieved at this point
>>> entry.blog # cached version, no DB access
但通常情况下,可调用属性每次都会导致数据库查找:
>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all() # query performed
>>> entry.authors.all() # query performed again
阅读模板代码时要小心-模板系统不允许使用括号,但会自动调用可调用文件,隐藏上述区别。
注意您自己的自定义属性-需要时由您来实现缓存,例如使用 cached_property
装饰符。
with
模板标签¶利用 QuerySet
,您可能需要使用 with
模板标签。
iterator()
¶当您有很多对象时, QuerySet
可能会导致使用大量内存。在这种情况下, iterator()
也许有帮助。
explain()
¶QuerySet.explain()
提供有关数据库如何执行查询的详细信息,包括使用的索引和联接。这些详细信息可以帮助您找到可以更高效地重写的查询,或者识别可以添加以提高性能的索引。
例如:
在最基本的层次上,使用 filter and exclude 在数据库中进行筛选。
使用 F expressions
根据同一模型中的其他字段进行筛选。
如果这些还不足以生成所需的SQL:
RawSQL
¶一种不太便携但更强大的方法是 RawSQL
表达式,允许将某些SQL显式添加到查询中。如果这还不够强大:
自己写 custom SQL to retrieve data or populate models . 使用 django.db.connection.queries
为了找出 Django 在为你写什么,从那里开始。
使用列的原因有两个 unique
或 db_index
使用时 get()
检索单个对象。首先,由于基础数据库索引,查询将更快。此外,如果多个对象与查找匹配,则查询的运行速度可能会慢得多;对列具有唯一的约束可以保证永远不会发生这种情况。
因此,使用 example blog models :
>>> entry = Entry.objects.get(id=10)
将比:
>>> entry = Entry.objects.get(headline="News Item Title")
因为 id
由数据库索引,并保证是唯一的。
执行以下操作可能非常缓慢:
>>> entry = Entry.objects.get(headline__startswith="News")
首先, headline
没有索引,这将使基础数据库获取速度变慢。
第二,查找不能保证只返回一个对象。如果查询匹配多个对象,它将从数据库中检索和传输所有对象。如果返回成百上千条记录,这种惩罚可能是巨大的。如果数据库驻留在一个单独的服务器上,那么惩罚将更加复杂,其中网络开销和延迟也是一个因素。
对于单个“数据集”的不同部分多次访问数据库,您将需要其中的所有部分,一般来说,比在一个查询中检索所有部分效率低。如果您有一个在循环中执行的查询,因此在只需要一个查询的情况下,最终可能会执行许多数据库查询,那么这一点尤其重要。所以:
QuerySet.values()
和 values_list()
¶当你只想 dict
或 list
不需要ORM模型对象,适当使用 values()
. 这些对于替换模板代码中的模型对象很有用——只要您提供的dict与模板中使用的dict具有相同的属性,就可以了。
QuerySet.defer()
和 only()
¶使用 defer()
和 only()
如果有数据库列,您知道不需要(或在大多数情况下不需要)来避免加载它们。注意,如果你 do 使用它们,ORM必须将它们放到一个单独的查询中,如果不恰当地使用它,这将是一个错误的模拟。
不要在没有分析的情况下过于激进地延迟字段,因为数据库必须从磁盘读取结果中的单个行的大多数非文本、非`VARCHAR``数据,即使它最终只使用了几列。这个 defer()
和 only()
当您可以避免加载大量文本数据,或者对于可能需要进行大量处理才能转换回Python的字段时,方法是最有用的。一如既往,首先分析,然后优化。
QuerySet.contains(obj)
¶如果你只想知道 obj
在查询集中,而不是 if obj in queryset
。
QuerySet.count()
¶…如果你只想数数,而不是做 len(queryset)
.
QuerySet.exists()
¶…如果您只想找出是否存在至少一个结果,而不是 if queryset
.
但是:
contains()
, count()
,以及 exists()
¶如果您需要来自QuerySet的其他数据,请立即对其求值。
例如,假设一个 Group
与具有多对多关系的模型 User
,以下代码是最佳代码:
members = group.members.all()
if display_group_members:
if members:
if current_user in members:
print("You and", len(members) - 1, "other users are members of this group.")
else:
print("There are", len(members), "members in this group.")
for member in members:
print(member.username)
else:
print("There are no members in this group.")
它是最佳的,因为:
由于QuerySet是惰性的,因此在以下情况下不会进行数据库查询 display_group_members
是 False
。
储存 group.members.all()
在 members
变量允许重复使用其结果缓存。
这条线 if members:
成因 QuerySet.__bool__()
被调用,这会导致 group.members.all()
要在数据库上运行的查询。如果没有任何结果,它将返回 False
,否则 True
。
这条线 if current_user in members:
检查用户是否在结果缓存中,这样就不会发出其他数据库查询。
对.的使用 len(members)
打电话 QuerySet.__len__()
,重用结果缓存,因此同样不会发出任何数据库查询。
这个 for member
循环遍历结果缓存。
总而言之,此代码要么执行一个数据库查询,要么执行零个数据库查询。唯一经过深思熟虑的优化是使用 members
变量。vbl.使用 QuerySet.exists()
对于 if
, QuerySet.contains()
对于 in
,或 QuerySet.count()
对于计数,每一个都会引起额外的查询。
QuerySet.update()
和 delete()
¶不要检索一批对象、设置一些值并单独保存它们,而是使用大容量的SQL更新语句,通过 QuerySet.update() . 同样,做 bulk deletes 在可能的情况下。
但是请注意,这些批量更新方法不能调用 save()
或 delete()
单个实例的方法,这意味着您为这些方法添加的任何自定义行为都不会被执行,包括从普通数据库对象驱动的任何行为。 signals .
如果您只需要一个外键值,请使用已经存在于您所拥有的对象上的外键值,而不是获取整个相关对象并获取其主键。即:
entry.blog_id
而不是::
entry.blog.id
排序不自由;要排序的每个字段都是数据库必须执行的操作。如果模型具有默认顺序 (Meta.ordering
)你不需要它,把它放在 QuerySet
通过调用 order_by()
没有参数。
向数据库中添加索引可能有助于提高排序性能。
使用批量方法来减少SQL语句的数量。
创建对象时,如果可能,请使用 bulk_create()
方法来减少SQL查询的数量。例如::
Entry.objects.bulk_create(
[
Entry(headline="This is a test"),
Entry(headline="This is only a test"),
]
)
…优于:
Entry.objects.create(headline="This is a test")
Entry.objects.create(headline="This is only a test")
注意有很多 caveats to this method
,因此请确保它适合您的用例。
更新对象时,如果可能,请使用 bulk_update()
方法来减少SQL查询的数量。给定一个对象列表或查询集:
entries = Entry.objects.bulk_create(
[
Entry(headline="This is a test"),
Entry(headline="This is only a test"),
]
)
以下示例:
entries[0].headline = "This is not a test"
entries[1].headline = "This is no longer a test"
Entry.objects.bulk_update(entries, ["headline"])
…优于:
entries[0].headline = "This is not a test"
entries[0].save()
entries[1].headline = "This is no longer a test"
entries[1].save()
注意有很多 caveats to this method
,因此请确保它适合您的用例。
将对象插入 ManyToManyFields
使用 add()
以减少多个SQL对象的数量。例如::
my_band.members.add(me, my_friend)
…优于:
my_band.members.add(me)
my_band.members.add(my_friend)
…在哪里 Bands
和 Artists
有多对多的关系。
将不同的对象对插入到 ManyToManyField
或者当习俗 through
表已定义,使用 bulk_create()
方法来减少SQL查询的数量。例如::
PizzaToppingRelationship = Pizza.toppings.through
PizzaToppingRelationship.objects.bulk_create(
[
PizzaToppingRelationship(pizza=my_pizza, topping=pepperoni),
PizzaToppingRelationship(pizza=your_pizza, topping=pepperoni),
PizzaToppingRelationship(pizza=your_pizza, topping=mushroom),
],
ignore_conflicts=True,
)
…优于:
my_pizza.toppings.add(pepperoni)
your_pizza.toppings.add(pepperoni, mushroom)
…在哪里 Pizza
和 Topping
有多对多的关系。请注意,有许多 caveats to this method
,因此请确保它适合您的用例。
从中移除对象时 ManyToManyFields
使用 remove()
以减少多个SQL对象的数量。例如::
my_band.members.remove(me, my_friend)
…优于:
my_band.members.remove(me)
my_band.members.remove(my_friend)
…在哪里 Bands
和 Artists
有多对多的关系。
从中移除不同的对象对时 ManyToManyFields
使用 delete()
在一 Q
带多个 through
模型实例以减少SQL查询的数量。例如::
from django.db.models import Q
PizzaToppingRelationship = Pizza.toppings.through
PizzaToppingRelationship.objects.filter(
Q(pizza=my_pizza, topping=pepperoni)
| Q(pizza=your_pizza, topping=pepperoni)
| Q(pizza=your_pizza, topping=mushroom)
).delete()
…优于:
my_pizza.toppings.remove(pepperoni)
your_pizza.toppings.remove(pepperoni, mushroom)
…在哪里 Pizza
和 Topping
有多对多的关系。
12月 18, 2023