Django程序的优化技巧
作者:大江狗 发布时间:2023-11-10 00:29:24
目录
友情提示:
性能优化指标
数据库查询优化
利用Queryset的惰性和缓存,避免重复查询
一次查询所有需要的关联模型数据
仅查询需要用到的数据
使用分页,限制最大页数
数据库设置优化
缓存
视图缓存
使用@cached_property装饰器缓存计算属性
缓存临时性数据比如sessions
模版缓存
静态文件
友情提示:
过度性能优化是没有必要甚至有害的,因为花大力气带来的毫秒级的响应提升你的用户可能根本感知不到,毕竟开发人员的时间也很宝贵。
性能优化指标
在对一个Web项目进行性能优化时,我们通常需要评价多个指标:
响应时间
最大并发连接数
代码的行数
函数调用次数
内存占用情况
CPU占比
其中响应时间(服务器从接收用户请求,处理该请求并返回结果所需的总的时间)通常是最重要的指标,因为过长的响应时间会让用户厌倦等待,转投其它网站或APP。当你的用户数量变得非常庞大,如何提高最大并发连接数,减少内存消耗也将变得非常重要。
在开发环境中,我们一般建议使用django-debug-toolbar和django-silk来进行性能监测分析。它们提供了每次用户请求的响应时间,并告诉你程序执行过程哪个环节(比如SQL查询)最消耗时间。
对于中大型网站或Web APP而言,最影响网站性能的就是数据库查询部分了。一是反复从数据库读写数据很消耗时间和计算资源,二是当返回的查询数据集queryset非常大时还会占据很多内存。我们先从这部分优化做起。
数据库查询优化
利用Queryset的惰性和缓存,避免重复查询
充分利用Django的QuerySet的惰性和自带缓存特性,可以帮助我们减少数据库查询次数。比如下例中例1比例2要好。因为在你打印文章标题后,Django不仅执行了数据库查询,还把查询到的article_list放在了缓存里,下次可以在其它地方复用,而例2就不行了。
# 例1: 利用了缓存特性 - Good
article_list = Article.objects.filter(title__contains="django")
for article in article_list:
print(article.title)
# 例2: Bad
for article in Article.objects.filter(title__contains="django"):
print(article.title)
但有时我们只希望了解查询的结果是否存在或查询结果的数量,这时可以使用exists()和count()方法,如下所示。这样就不会浪费资源查询一个用不到的数据集,还可以节省内存。
# 例3: Good
article_list = Article.objects.filter(title__contains="django")
if article_list.exists():
print("Records found.")
else:
print("No records")
# 例4: Good
count = Article.objects.filter(title__contains="django").count()
一次查询所有需要的关联模型数据
假设我们有一个文章(Article)模型,其与类别(Category)是单对多的关系(ForeignKey), 与标签(Tag)是多对多的关系(ManyToMany)。我们需要编写一个article_list的函数视图,以列表形式显示文章清单及每篇文章的类别和标签,你的模板文件可能如下所示:
{% for article in articles %}
<li>{{ article.title }} </li>
<li>{{ article.category.name }}</li>
<li>
{% for tag in article.tags.all %}
{{ tag.name }},
{% endfor %}
</li>
{% endfor %}
在模板里每进行一次for循环获取关联对象category和tag的信息,Django就要单独进行一次数据库查询,造成了极大资源浪费。我们完全可以使用select_related方法和prefetch_related方法一次性从数据库获取单对多和多对多关联模型数据,这样在模板中遍历时Django也不会执行数据库查询了。
# 仅获取文章数据 - Bad
def article_list(request):
articles = Article.objects.all()
return render(request, 'blog/article_list.html',{'articles': articles, })
# 一次性提取关联模型数据 - Good
def article_list(request):
articles = Article.objects.all().select_related('category').prefecth_related('tags')
return render(request, 'blog/article_list.html', {'articles': articles, })
仅查询需要用到的数据
默认情况下Django会从数据库中提取所有字段,但是当数据表有很多列很多行的时候,告诉Django提取哪些特定的字段就非常有意义了。假如我们数据库中有100万篇文章,需要循环打印每篇文章的标题。如果按例4操作,我们会将每篇文章对象的全部信息都提取出来载入到内存中,不仅花费更多时间查询,还会大量占用内存,而最后只用了title这一个字段,这是完全没有必要的。我们完全可以使用values和value_list方法按需提取数据,比如只获取文章的id和title,节省查询时间和内存(例6-例8)。
# 例子5: Bad
article_list = Article.objects.all()
if article_list:
print(article.title)
# 例子6: Good - 字典格式数据
article_list = Article.objects.values('id', 'title')
if article_list:
print(article.title)
# 例子7: Good - 元组格式数据
article_list = Article.objects.values_list('id', 'title')
if article_list:
print(article.title)
# 例子8: Good - 列表格式数据
article_list = Article.objects.values_list('id', 'title', flat=True)
if article_list:
print(article.title)
除此以外,Django项目还可以使用defer和only这两个查询方法来实现这一点。第一个用于指定哪些字段不要加载,第二个用于指定只加载哪些字段。
使用分页,限制最大页数
事实前面代码可以进一步优化,比如使用分页仅展示用户所需要的数据,而不是一下子查询所有数据。同时使用分页时也最好控制最大页数。比如当你的数据库有100万篇文章时,每页即使展示100篇,也需要1万页展示给你的用户,这是完全没有必要的。你可以完全只展示前200页的数据,如下所示:
LIMIT = 100 * 200
data = Articles.objects.all()[:(LIMIT + 1)]
if len(data) > LIMIT:
raise ExceededLimit(LIMIT)
return data
数据库设置优化
如果你使用单个数据库,你可以采用如下手段进行优化:
建立模型时能用CharField确定长度的字段尽量不用不用TextField, 可节省存储空间;
可以给搜索频率高的字段属性,在定义模型时使用索引(db_index=True);
持久化数据库连接。
没有持久化连接,Django每个请求都会与数据库创建一个连接,直到请求结束,关闭连接。如果数据库不在本地,每次建立和关闭连接也需要花费一些时间。设置持久化连接时间,仅需要添加CONN_MAX_AGE参数到你的数据库设置中,如下所示:
DATABASES = {
‘default': {
‘ENGINE': ‘django.db.backends.postgresql_psycopg2',
‘NAME': ‘postgres',
‘CONN_MAX_AGE': 60, # 60秒
}
}
当然CONN_MAX_AGE也不宜设置过大,因为每个数据库并发连接数有上限的(比如mysql默认的最大并发连接数是100个)。如果CONN_MAX_AGE设置过大,会导致mysql 数据库连接数飙升很快达到上限。当并发请求数量很高时,CONN_MAX_AGE应该设低点,比如30s, 10s或5s。当并发请求数不高时,这个值可以设得长一点,比如60s或5分钟。
当你的用户非常多、数据量非常大时,你可以考虑读写分离、主从复制、分表分库的多数据库服务器架构。这种架构上的布局是对所有web开发语言适用的,并不仅仅局限于Django,这里不做进一步展开了。
缓存
缓存是一类可以更快的读取数据的介质统称,也指其它可以加快数据读取的存储方式。一般用来存储临时数据,常用介质的是读取速度很快的内存。一般来说从数据库多次把所需要的数据提取出来,要比从内存或者硬盘等一次读出来付出的成本大很多。对于中大型网站而言,使用缓存减少对数据库的访问次数是提升网站性能的关键之一。
视图缓存
from django.views.decorators.cache import cache_page
@cache_page(60 * 15)
def my_view(request):
...
使用@cached_property装饰器缓存计算属性
对于不经常变动的计算属性,可以使用@cached_property装饰器缓存结果。
缓存临时性数据比如sessions
Django的sessions默认是存在数据库中的,这样的话每一个请求Django都要使用sql查询会话数据,然后获得用户对象的信息。对于临时性的数据比如sessions和messages,最好将它们放到缓存里,也可以减少SQL查询次数。
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
模版缓存
默认情况下Django每处理一个请求都会使用模版加载器都会去文件系统搜索模板,然后渲染这些模版。你可以通过使用cached.Loader开启模板缓存加载。这时Django只会查找并且解析你的模版一次,可以大大提升模板渲染效率。
TEMPLATES = [{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'OPTIONS': {
'loaders': [
('django.template.loaders.cached.Loader', [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
'path.to.custom.Loader',
]),
],
},
}]
注意:不建议在开发环境中(Debug=True)时开启缓存加载,因为修改模板后你不能及时看到修改后的效果。
另外模板文件中建议使用with标签缓存视图传来的数据,便于下一次时使用。对于公用的html片段,也建议使用缓存。
{% load cache %}
{% cache 500 sidebar request.user.username %}
.. sidebar for logged in user ..
{% endcache %}
静态文件
压缩 HTML、CSS 和 JavaScript等静态文件可以节省带宽和传输时间。Django 自带的压缩工具有GzipMiddleware 中间件和 spaceless 模板 Tag。使用Python压缩静态文件会影响性能,一个更好的方法是通过 Apache、Nginx 等服务器来对输出内容进行压缩。例如Nginx服务器支持gzip压缩,同时可以通过expires选项设置静态文件的缓存时间。
来源:https://mp.weixin.qq.com/s/U07oOF8t04H8jyNv0TMJFw
猜你喜欢
- 一、系统的默认用户1)sys用户是超级用户,具有最高权限,具有sysdba角色,有create database的权限,该用户默认的密码是s
- 前言日常工作中,在不刷新页面的情况下发送消息并获得即时响应是我们认为理所当然的事情。但在过去,启用实时功能对开发人员来说是一个真正的挑战。开
- 目录前言1.insert ignore into2.on duplicate key update3.replace into4.inser
- 一、需求说明:数据库的备份,对于生产环境来说尤为重要,数据库的备份分为物理备份和逻辑备份。物理备份:使用相关的复制命令直接将数据库的数据目录
- 我们把一个事物进行分解,就可以得到其中的元素。对于python中的解包来说,也是同样的使用,相信大家从名称就可以观察出来了。那么在具体的解包
- 掩码数组数据很大形况下是凌乱的,并且含有空白的或者无法处理的字符,掩码式数组可以很好的忽略残缺的或者是无效的数据点。掩码式数组由一个正常数组
- pytorch自定义不可导激活函数今天自定义不可导函数的时候遇到了一个大坑。首先我需要自定义一个函数:sign_fimport torchf
- 偶然遇到的问题,记录如下:通常我们在push项目时,会有些配置文件或本地文件不想上传到服务器上这时候我们会通过设置.gitignore&nb
- 如下所示:import re# 过滤不了\\ \ 中文()还有————r1 = u'[a-zA-Z0-9'!"#$
- 概要贝叶斯网络是一种基于概率的图模型,可用于建立变量之间的条件概率关系。在拼写检查器中,贝叶斯网络可以通过建立一个隐含状态、错误观察值和正确
- 本文实例为大家分享了JavaScript实现淘宝网图片的局部放大的具体代码,供大家参考,具体内容如下要实现的效果如下:<!DOCTYP
- 通常测试人员或公司实习人员需要处理一些txt文本内容,而此时使用Python是比较方便的语言。它不光在爬取网上资料上方便,还在NLP自然语言
- 1、在Python中以相对路径或者绝对路径来导入文件或者模块的方法今天在调试代码的时候,程序一直提示没有该模块,一直很纳闷,因为我导入文件一
- SQL2008清空删除日志:方法一:USE [master]GOALTER DATABASE AFMS SET RECOVERY SIMPL
- 1、plotly库的相关介绍1)相关说明plotly是一个基于javascript的绘图库,plotly绘图种类丰富,效果美观;易于保存与分
- 左右结构是平常页面中最经常看到的结构,简洁一些的页面就会使用边框将左右两边隔开,但往往由于左右两边的内容可能是不等高的,所以就会有一高一低的
- 最近工作中需要完成一个评论的功能,上网查找了几个评论系统的展示样式。最后参考“多说”和“畅言”等评论系统,自己使用PHP语言实现了一个简单的
- 环境准备数据库版本:MySQL 5.7.20-log建表 SQLDROP TABLE IF EXISTS `t_ware_sale_stat
- __isset() – 在对类中属性或者非类中属性使用isset()方法的时候如果没有或者非公有属性,则自动执行__isset(
- 1.ROW_NUMBER()基本用法:SELECT SalesOrderID, CustomerID,