Django的数据库层提供了很多方法来帮助开发者充分的利用他们的数据库。这篇文档上聚 合了相关的文档链接,同时附加了一些小技巧,当你打算优化你对数据库的使用时,可以 按照下面组织好的话题一步步来做。
作为通用的编程实践,性能的重要性不言而喻。找出 `你执行了多少次查询以及它们花费了你多少时间 <faq-see-raw-sql-queries>`。你或许想要通过一个外部的项目比如 django-debug-toolbar, 或者一个工具可以直接的监控你的数据库。
记住,你可能会优化速度或内存甚至两者都优化,这取决于你的需求。有时对其中一项的优化会 不利于另一项,但有时也会相互得益。同样,让数据库来处理所有的工作和你用Python来处理 所花费的时间是不同的(对你来说)。哪一个优先级高些取决于你,你必须找到一个平衡点, 同时,衡量所有这些需求,因为这将依赖于你的应用程序和服务器。
按照下面的方法,切记对每一个改变做性能分析,以确保每次改变都是有益的,但是足够大的优 化可能会降低代码的可读性。**所有**下面这些建议都伴随着一个警告:在你所处的情况和原则 下,这些建议可能并不适用,甚至可能是有副作用的。
...包括:
django.db.models.Field.db_index 从Django中添加索引。
使用适当的字段类型。
我们假设你已经完成了上面说的显而易见的优化。这篇文档剩下的部分将关注在如何用这样 的方式使用Django以避免不必要的工作。这篇文档并没有讨论其他的适用于花费时间较多操作的 优化技巧,比如 :doc:`通用缓存</topics/cache>`。
理解 QuerySets 是通过简单的代码获取较好性能至关重要 的一步。尤其是:
除了缓存整个 ``QuerySet``之外,还会缓存ORM对象由属性获取到的结果。通常,不可被 调用的属性会被缓存。比如,假设这是 Weblog models示例:
>>> entry = Entry.objects.get(id=1)
>>> entry.blog # 此时的Blog对象会从数据库取出
>>> entry.blog # 这时的blog是缓存的版本,不会造成数据库访问
但是一般来说,可被调用的属性每次都会触发数据库查询:
>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all() # 执行查询
>>> entry.authors.all() # 再次执行查询
当阅读模板里的代码时应该注意 - 模板系统不允许使用括号,但它会自动调用可被调 用的属性,这隐藏了上面的差别。
小心的对待你自定义的属性 - 它需要由你来实现缓存。
当你需要大量的对象时, QuerySet 的缓存行为会引起大量的内存使用。这种情况 下, iterator() 可能是有帮助的。
比如:
如果这些还不足以生成你需要的SQL的话,继续往下看:
自己来写 自定义的SQL来获取数据或者添加你定义的models 。使用 django.db.connection.queries 来查看Django帮你写了什么SQL,从这里 入手。
多次访问数据库来分别获取一个你需要的数据集合的不同部分,通常来说,效率要低于 在一次请求中获取所有数据。这是很重要的,如果你在一个循环里执行查询,而且可能 会因此导致很多的数据库查询,当只需要一次查询的时候。因此:
当你仅仅只想获取一个 dict 或者 list 的值,并且不需要使用ORM模型的 objects时,适当地使用 :meth:`~django.db.models.query.QuerySet.values()`。 这些对于把模型对象替换为模板代码很有用 - 只要你提供的dicts和你在模板中使用 的数据有相同的属性,你就不会出错 。
当你知道数据库中的一些字段是你不需要的(或大多数情况不需要),为了避免加载 这些数据,你可以使用 defer() 和
only() 。注意,如果你确实这么做了
,ORM将会不得不生成一个单独查询来获取这些数据,如果使用不当的话,会导致性能 变差。
同时,要意识到当构建一个带有延迟字段的模型时,在Django内部会有一些(小额的)额外 的开销。在没有进行性能分析之前不要太积极的使用延迟字段,因为数据库必须从磁盘上 读取一些非文本、非字符的数据作为结果中的一行,即使最终你只用到了很少的字段。 当你要避免加载很多文本数据或者针对那些可能会需要大量处理才能转为Python对象的 字段时, defer() 和 only() 方法是很有用的。和之前一样,先分析性能,然后 优化。
...如果你只是想要获取有多少数据,不要使用 len(queryset) 。
如果你需要从QuerySet中获取其他数据,那要先评估一下。
比如,假设有一个Email的model,有一个 body 的属性和一个多对多关系的User 属性,下面的模板代码是最优的:
{% if display_inbox %}
{% with emails=user.emails.all %}
{% if emails %}
<p>You have {{ emails|length }} email(s)</p>
{% for email in emails %}
<p>{{ email.body }}</p>
{% endfor %}
{% else %}
<p>No messages today.</p>
{% endif %}
{% endwith %}
{% endif %}
它是最优的是因为:
总之,这段代码将会执行一次或者零次数据库查询。唯一值得思考的最优的执行操作是 因为用了 with 标签。使用 QuerySet.exists() 或者 QuerySet.count() 在任何环境中都会引起额外的查询。
比起获取大量数据,对其中一些赋值,然后单独保存,最好使用批量的SQL更新语句, 通过 QuerySet.update() 。同样,尽可能使用
批量的删除操作 。
执行,包括任何从正常数据库对象发送过来的驱动信号。
如果你仅仅是需要外键的值,使用你所使用对象已经获取到的外键值,而不是获取整个关系 对象然后拿到它的主键。比如,这样做:
entry.blog_id
而不是:
entry.blog.id
当创建对象时,尽可能的使用 bulk_create() 方法来减少SQL查询的的 数量。比如:
Entry.objects.bulk_create([
Entry(headline="Python 3.0 Released"),
Entry(headline="Python 3.1 Planned")
])
...要好于:
Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")
注意,有很多关于这个方法的说明 :meth:`对这些方法的警告<django.db.models.query.QuerySet.bulk_create>`,因此, 确认这些方法适用于你所处的情景.
这同样适用于 ManyToManyFields, 因此,这么做:
my_band.members.add(me, my_friend)
...而不是这么做:
my_band.members.add(me)
my_band.members.add(my_friend)
...这里 Bands 和 Artists 是多对多的关系。
Dec 14, 2013