「Django」数据库访问优化

摘要:
首先执行性能分析-两个工具django。数据库。来自django的连接。dbimportconnection#上下文打印连接。查询#content“”结果:[{'时间':'0.002','sql':u'sELECT `django_session'.'session_key',`django-session'.'会话_d
先做性能分析 - 两个工具
  • django.db.connection
from django.db import connection
# context
print connection.queries
# content
''' result is:
[{
'time': '0.002',
'sql': u'SELECT `django_session`.`session_key`, `django_session`.`session_data`, `django_session`.`expire_date` FROM `django_session` WHERE (`django_session`.`session_key` = 5584f8d708ddc2d5e32831885fc36084 AND `django_session`.`expire_date` > 2013-05-07 10:39:36 )'}]
'''
  • django_debug_toolbar link
标准的数据库优化技巧
  • Indexes, 分析应该添加什么样的索引,使用 django.db.models.Field.db_index
  • 使用对应的字段类型
title = models.CharField(max_length=100, blank=True, db_index=True, verbose_name=u'标题')
理解QuerySets

理解QuerySet的求值过程

  • QuerySets是惰性的
news_list = News.object.all()
# 此时并未执行数据库查询
print news_list # 用时方执行查询操作
  • 何时它们被执行.
# 用时方执行查询操作
print news_list
  • 数据如何被缓存
# 这样的QuerySet没有被缓存
print([e.headline for e in Entry.objects.all()])
print([e.pub_date for e in Entry.objects.all()])
# 这么做
entries = Entry.objects.all()
print([e.headline for e in entries])

理解被缓存的属性

  • QuerySet 会被缓存
  • 不可被调用的属性会被缓存
>>> news = News.objects.get(id=1)
>>> news.channel # 此时的channel对象会从数据库取出
>>> news.channel # 这时的channel是缓存的版本,不会造成数据库访问
  • 方法的调用每次都会触发数据库查询
>>> news = News.objects.get(id=1)
>>> news.authors.all() # 执行查询
>>> news.authors.all() # 再次执行查询
  • 注意
  • 模板系统不允许使用括号,但它会自动调用可被调用的属性
  • 自定义的属性需要由你来实现缓存。

使用with模板标签

在模板中使用QuerySet缓存,需要使用with标签

使用iterator()

获取大量数据时

news_list = News.objects.filter(title__contains=u'违法')
for news in news_list.iterator():
print news
让数据库做它自己的工作

基本概念

  • 使用 filter and exclude 在数据库层面执行过滤操作
news_list = News.objects.filter(title__contains=u'和谐').exclude(status=1)
  • 使用 F() object query expressions 在同一模型中使用不同字段进行对比过滤
# 查询所有title和sub_title相同的数据
queryset = News.objects.filter(title=F('sub_title'))
  • 使用 注解
# 给每个对象添加一个news_count的属性
cl = Channel.objects.filter(parent__id=1).annotate(news_count=Count('news'))
print cl[0].news_count
  • 如果这些还不足以生成你需要的SQL的话,继续往下看:

使用 QuerySet.extra()

显式的执行SQL语句

cl = Channel.objects.filter(parent__id=1).extra(
select={
'another_news_count': 'SELECT COUNT(*) FROM web_news WHERE web_news.channel_id = web_channel.id'
}
)
print cl[0].another_news_count

使用原生的SQL

cl = Channel.objects.raw('SELECT * FROM web_channel WHERE parent_id = 1')
print cl
# <RawQuerySet: 'SELECT * FROM web_channel WHERE parent_id = 1'>
for c in cl:
print c
预加载数据

尽量一次加载你需要的数据

  • QuerySet.select_related() ,针对foreign key 和 one-to-one
news = News.objects.select_related().get(id=372924135)
print news.channel # 不会访问数据库
  • QuerySet.prefetch_related() ,1.4中存在, 和select_related()类似,针对many-to-many
不要获取你不需要的数据

使用 QuerySet.values() 和 values_list()

当只需要一个字段的值,返回list或者dict时,使用

  • values
news_list = News.objects.values('title').filter(channel__id=1)
print news_list
# [{'title': ''}, ...]
  • values_list
news_list = News.objects.values_list('title').filter(channel__id=1)
print news_list
# [('新闻标题',),('新闻标题', ) ...]

使用 QuerySet.defer() 和 only()

  • QuerySet.defer() 来延迟加载某字段,加载时会产生额外查询
news_list = News.object.defer('title').all()
n = news_list[0]
print n.title # 会产生额外的查询语句
  • QuerySet.only() 只加载某字段,之后读取任何属性都会产生查询

使用 QuerySet.count()

如果你只是想要获取有多少数据,不要使用 len(queryset) 。

nl = News.objects.filter(channel__id=2)
nl.count()
# SELECT COUNT(*) FROM `web_news` WHERE `web_news`.`channel_id` = 2 ; 'time': '0.014'
len(nl)
# 'time': '0.422'

使用 QuerySet.exists()

如果你只是想要知道是否至少存在一个结果,不要使用 if querysets 。

不要过度使用 count() 和 exists()

比如,假设有一个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 %}

它是最优的是因为:

  • 因为QuerySet是惰性的,如果 'display_inbox' 是False的话,这不会产生数据库查询。
  • 使用 with 意味着我们会存储 user.emails.all 在一个变量中供后面使用,这允许被缓存以便重用。
  • {% if emails %} 其实是调用 QuerySet.__nonzero__() ,在数据库层面执行 user.emails.all() ,然后返回结果,放入缓存。
  • {{ emails|length }} 的使用将调用 QuerySet.__len__(),数据已在缓存
  • for 循环的email数据已经在缓存中了。
  • with的使用是关键
  • 每次的QuerySet.count()调用都会产生查询

使用 QuerySet.update() 和 delete()

  • 批量更新使用 QuerySet.update()
  • 批量删除使用 QuerySet.delete()
  • 批量操作不会调用类中定义的 save() 或 delete() 方法

直接使用外键的值

获取频道ID:

news.channel_id

而不是:

news.channel.id
批量插入
  • 用 django.db.models.query.QuerySet.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")
  • 这同样适用于 ManyToManyFields, 因此,这么做
team.members.add(me, my_friend)
  • ...而不是这么做
team.members.add(me)
team.members.add(my_friend)
  • ...这里 team 和 members 是多对多的关系。

免责声明:文章转载自《「Django」数据库访问优化》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Docker之构建上下文详解为什么很多公司都在招测试开发?下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

django的数据库ORM进阶操作

1、数据库查询的三种方式 正向查询: (1)获取全部数据all     v1=models.UserInfo.objects.all().first()    #取出Querryset类型数据,里面是对象,需循环取出     v1.ut    #跨表,ut是外键 (2)只取部分字段value,  外键__字段     v2=models.UserInfo....

Django项目的docker镜像制作

最近搞了个django项目,在各个环境部署时总会遇到问题,于是便想到了docker部署,接下来记录一下一下docker镜像制作部署的流程。 基础镜像:镜像中运行的项目或者启动的一些服务,都要在一个基础镜像之上才能运行这些服务,比如一个django项目或者mysql数据库等,都要在Linux操作系统之上来运行,所以打包我们自己的项目时,必须要有个基础镜像来当...

安装django(使用国内镜像)

1.安装django之前需要安装python及pip, python与django的对应版本请自行百度 2.安装之后,需要配置python及pip的环境变量,具体配置方法请自行百度 3.django的安装:使用pip install django 默认安装最新版本的django 安装时候若有如下报错: File "c:installpython37libs...

Django知识总结(一)

壹 ● 有关http协议 一 ● 全称 超文本传输协议(HyperText Transfer Protocol) 二 ● 协议 双方遵循的规范 http协议是属于应用层的协议(还有ftp, smtp等), 即浏览器请求消息和服务器响应消息的一系列规则 三 ● http协议的特性 http是无状态、无连接的协议(stateless, c...

linux:nohup后台启动django

前言 django服务在linux启动后,python manage.py runserver 0.0.0.0:8000启动服务。但是这样有个弊端,窗口关闭服务就停止了。 通过nohup 可以挂后台启动,服务一直运行。 nohup后台启动 在使用nohup之前,现将无关的后台服务关闭,这样节省运行资源,提升启动速度。 输入命令行:ps -aux | gre...

Windows下Python虚拟环境安装及新建django项目及简单流程

---恢复内容开始--- (1-7)为第一种创建虚拟环境的方法 1. 安装Python 2. 在cmd命令行中使用pip安装:pip install virtualenv 3. 新建虚拟环境:virtualenv testvir 4. 进入该虚拟环境:cd testvir 5. cd Scripts 6. activate.bat就进入了该虚拟环境 如图所...