..

记一次 Django 项目优化

把之前的一个 web 项目重写了一遍,语言从原先的 PHP 换成 Python,使用了 Django 框架。项目因为数据库比较庞大,有 600M 左右的 sql 数据,一些应用涉及到查询和修改时,导致网页操作十分缓慢。同时因为也是刚开始接触 Django,有些代码写得比较蠢,后来优化之后网页操作的速度基本可以接受。故写这篇文章记录一下。

模版自定义函数

因为数据库设计原因,从数据库提取出来的某个字段的数据还需要经过一次 base64 加密,原先是将筛选出来的 QuerySet 用 for 循环去遍历然后修改。但是,如果直接用 for 循环去遍历 QuerySet,Django 会把他们全部进行实例化,因为数据量本身也比较大的原因,会导致占用大量的内存。

因为我希望在不修改数据库和尽可能少的改动代码的前提下去解决问题,一番查阅资料之后发现自定义 Django 模版的函数是个不错的办法。

Django 模板里面有两种方式来自定义函数,分别是 simple_tag 和 filter 方式。实现的方式如下。

在对应 app 下创建一个名为 templatetags 的目录(不可修改目录名),在这个目录下创建一个名字任意的 py 文件,在这个 py 文件里导入模板类,实例化一个对象 register,然后执行一个相应的装饰器即可,文件代码如下:

from django import template
from django.utils.safestring import mark_safe

register = template.Library()

@register.simple_tag
def base64en(s):
    return base64.b64encode(s.encode('utf-8')).decode("utf-8")

@register.filter
def tobase64(s):
    return base64.b64encode(s.encode('utf-8')).decode("utf-8")

其中,base64en(simple_tag 方式) 和 tobase64(filter 方式) 是我自定义的函数。在模版文件里调用:

{% load addontags %}

{{ sample_str|tobase64 }}
{{ base64en sample_str }}

其中,addontags 是我定义函数的 py 文件名。

两种实现模式的区别为:

  • simple_tag 参数无个数限制,而 filter 最多只能有2个参数
  • simple_tag 不能作为 if 条件,而 filter 可以
  • simple_tag 后面的参数之间的空格随意,filter 不能有空格

我更倾向 filter 模式,比较好看,嘿嘿。完成之后打开相应页面,速度得到明显提升。

Model 操作优化

只获取需要的数据

默认情况下,ORM 查询的时候会把数据库记录对应的所有列取出,然后转换成 python 对象,这无疑是个很大的内存消耗。当我们只需要某些特定列的数据时我们可以通过 values() 或 values_list() 函数来实现,它们会将数据转换成 dicts、tuples等而不是复杂的 python 对象。

项目中有些 query 查询,只是用到了部分数据,我们可以通过 values() 函数简化结果。

XModel.objects.filter(xxx=yyy)[:1] # before

XModel.objects.filter(xxx=yyy)[:1].values('zzz') # after

使用 update() 代替 save()

Django 中使用 save() 更新数据时,数据库会执行两次操作(select, update),而使用 update() 时,数据库只执行一次(update)。如果只是更改数据而不需要用到查询结果,建议直接换成 update()。

# before
x = XModel.objects.filter(xxx=yyy)
x.zzz = 'aaa'
x.save()

# after
XModel.objects.filter(xxx=yyy).update(zzz='aaa')

使用 create() 代替 save()

同样的,Django 中使用 save() 插入数据时,数据库会执行两次操作(select, insert),而使用 create() 时,数据库只执行一次(insert)。如果只是为了创建新数据到数据库,建议直接换成 create()。

# before
x = XModel(zzz='aaa')
x.save()

# after
XModel.objects.create(zzz='aaa')

使用缓存

在我的 web 项目中,有几个页面其实不是数据读取特别频繁的页面,这些页面其实可以设置缓存时间,这样在一段时间内用户访问时读取缓存就好了。

因为服务器内存比较小,这里直接使用文件缓存,设置如下。

配置文件中:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': os.path.join(BASE_DIR, '.temp'),
    }
}

接着在需要缓存的路径方法中加入 cache_page 修饰符,例如:

@cache_page(86400)
def index(request):
	...

其中 cache_page 的参数即为缓存时间,单位为秒,86400 即一天。

总结

因为项目比较轻量,没有涉及到很复杂的数据交互,在完成上述优化之后,运行速度已经可以接受,个人还是比较满意的:)