Django入门教程

前言

第一章: django快速入门

第二章: django MTV架构

第三章: django视图

第四章: django模板

第五章: django模型

第六章: django后台管理系统

第七章: 项目实战-简易的博客系统

第八章:django表单

第九章:django用户认证系统

第十章:django中的会话

第十一章:django安全

第十二章:django性能优化

第十三章:django实用工具

首页 > Django入门教程 > 第十二章:django性能优化 > 12.3节:优化数据库性能

12.3节:优化数据库性能

薯条老师 2020-10-16 07:06:40 234368 0

编辑 收藏

系统地讲解Django开发的基础知识, 高阶知识。关注微信公众号[薯条编程],领取该教程的PDF电子书:《Python web开发-django从入门到精通》。

12.3.1 对数据库的优化

常规的数据库优化技术在于数据库的合理设计以及索引的优化。对于数据库的设计,需要根据实际的需求来优化表结构,采用合适的字段类型以及约束。对于索引的优化,可以在模型中通过Meta.indexes或Field.db_index选项,将频繁查询的字段加上索引,比如经常出现在filter()或order_by()方法中的字段。

做好了以上两点以后,接下来的优化工作,主要在于理解django中的数据库访问机制,以避免无必要的耗时查询。

12.3.2 理解QuerySets

(1) 惰性查询

django中的QuerySet采用的是惰性查询,只有在触发evaluate的时候,才会与底层的数据库进行交互。evaluate可以理解为在数据库中执行QuerySet中的查询语句。

(2) 什么时候触发evaluate

在对QuerySet对象执行以下操作时,会触发evaluate:

1.迭代

QuerySet 是可迭代的类型,在循环中遍历字段的值时会触发evaluate操作。

2.切片

QuerySet对象可以通过Python中的切片语法来进行切片,切片操作不支持负数索引,对未evaluate的QuerySet对象进行切片,返回的是一个新的QuerySet对象,除非在操作中使用了步长参数(切片语法中的step),才会被evaluate。

3.序列化/缓存化

对QuerySet对象进行序列化操作,也会进行evaluate。

4.repr()

对QuerySet对象执行repr()方法,会触发evaluate操作。

5.len()

调用 QuerySet 的 len() 方法,返回查询结果的数目。注意:直接在数据库层面使用 SQL 的 SELECT COUNT(*) 方法来获取数据的条数会更加高效在实际开发中,可以使用QuerySet对象的count()方法来替代len()。

6.list()

将QuerySet 对象转换为列表类型。注意:使用这个方法会占用大量内存,因为 django将列表内容都载入到内存中。

7.bool()

测试QuerySet对象的布尔值时,也会对queryset对象进行evaluate。

(3) QuerySet对象中的数据保存方式

QuerySet中包含一个缓存来最小化对数据库的访问。在一个新创建的QuerySet对象中,缓存为空。首次执行QuerySet中的查询语句,django会将返回的结果缓存到QuerySet对象中,在接下来对该QuerySet对象的求值将重用缓存的结果。读者请看下面的演示代码:

>>> print([blog.author for blog in Blog.objects.all()])

>>> print([blog.title for blog in Blog.objects.all()])

在上文的演示代码中,创建了两次对象来获取模型中的数据,没有使用缓存,效率低下。正确的做法是将第一次获取到的QuerySet对象保存下来,下次操作时,可以直接访问缓存的结果:

>>> queryset_for_blog = Blog.objects.all()

>>> print([blog.author for blog in queryset_for_blog])

>>> print([blog.title for blog in queryset_for_blog])

QuerySet对象使用切片或索引进行数据库的limit操作时,django不会将结果进行缓存,除非在此之前,整个QuerySet对象已触发evaluate操作。代码实例:

>>> queryset = Blog.objects.all()

>>> print(queryset[5]) # 从数据库中进行查询

>>> print(queryset[5]) # 仍从数据库中进行查询

QuerySet对象执行evaluate操作:

>>> queryset = Blog.objects.all()

>>> [blog for blog in queryset] # Queries the database

>>> print(queryset[5]) # 使用缓存

>>> print(queryset[5]) # 使用缓存

12.3.3 理解模型对象的缓存方式

对模型对象中的属性进行访问时,django也会对结果进行缓存:

>>> blog = Blog.objects.get(id=1)

>>> entry.author   # 从数据库中获取author值

>>> entry.author   # 从django缓存中获取author值

如果该模型的属性是一个可调用对象,则在进行调用时,不会对结果进行缓存:

>>> blog = Blog.objects.get(id=1)

>>> blog.authors.all()   # 在数据库中执行查询操作

>>> blog.authors.all()   # 在数据库中执行查询操作

12.3.4 在模板中使用with标签

为了确保与QuerySet对象一致的缓存行为,需要在模板中使用with标签:

{% with total=business.employees.count %}
    {{ total }} employee{{ total|pluralize }}
{% endwith %}

with标签复杂的操作简化为一个模板变量,从而对操作的结果进行缓存。

12.3.5 使用iterator()访问QuerySet

在对QuerySet对象执行list()等操作时,会占用大量内存,对于这种情况,可以使用iterator()对QuerySet对象进行迭代访问,从而节约内存。

12.3.6 使用RawSQL来进行查询

ORM的缺点在于,其充当一个中间层,与直接操作数据库相比,ORM会在一定程度上牺牲程序的执行性能。在django中使用RawSQL会直接在数据库中进行查询,无需通过ORM,这样可以提升部分性能,缺点是移植性较差。

12.3.7 使用索引来进行查询

通过索引来获取单独的对象时,会大幅提升查询性能。对未加索引的字段进行查询:

blog = Blog.objects.get(author="Backer")

由于author字段未加索引,所以数据库在执行查询操作时,会进行全表扫描,查询速度会变得异常缓慢。优化方案是对author字段加上索引,或使用其它已加索引的字段进行查询:

blog = Blog.objects.get(id=1)

12.3.8 对数据库执行批量操作

需要对大量数据进行操作时,采用批量操作是一个很有效的性能优化方案。采用批量操作可以大幅减少SQL语句执行的次数,从而获得性能提升。在django的ORM中进行批量操作,主要有以下方法:

(1) 使用builk_create批量创建对象

代码实例

Blog.objects.bulk_create([
    Blog(title='遇见python'),
    Blog(title='遇见django'),
])

(2) 使用build_update批量更新对象

代码实例:

blogs = Blog.objects.bulk_create([
    Blog(title='遇见python'),
    Blog(title='遇见django'),
])
blogs[0].title = '遇见python3.8'
blogs[1].title = '遇见django3.0'
Blog.objects.bulk_update(blogs, ['title'])

(3) 对于ManyToManyFields字段的批量操作

使用add方法以及delete方法来对ManyToManyFields字段进行关联对象的批量添加和删除。

使用add批量添加:

blog.authors.add(me, her)

要优于逐对象添加:

blog.authors.add(me)
blog.authors.add(her)

使用delete批量删除:

blog.authors.delete(her,others)

要优于逐对象删除:

blog.authors.delete(her)
blog.authors.add(others)

12.3.9 其它的优化建议

除以上列举的一些常用的优化方法之外,django还提供了其它的一些优化方法。例如在数据库中存在外键的时候,使用select_related()和prefech_related()方法可以减少数据库请求的次数,使用QuerySet.values()方法来返回部分字段,使用QuerySet.count()方法代替直接对QuerySet对象使用len方法来计算长度等等。

更详尽的优化细节,感兴趣的读者可以参考django的官方文档。

关注微信公众号:薯条编程,公众号后台回复"Python资料",免费领取Python电子书,以及学习Python视频课程。

小班授课,薯条老师一对一教学,火热报名中,点击了解线下就业培训。


欢迎 发表评论: