「Django」数据库访问优化
先做性能分析 - 两个工具
- 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」数据库访问优化的更多相关文章
- 数据库访问优化漏斗法则- 四、减少数据库服务器CPU运算
数据库访问优化漏斗法则这个优化法则归纳为5个层次:1.减少数据访问次数(减少磁盘访问)2.返回更少数据(减少网络传输或磁盘访问)3.减少交互次数(减少网络传输)4.减少服务器CPU开销(减少CPU及内 ...
- Django查询数据库性能优化
现在有一张记录用户信息的UserInfo数据表,表中记录了10个用户的姓名,呢称,年龄,工作等信息. models文件 from django.db import models class Job(m ...
- 高效解决「SQLite」数据库并发访问安全问题,只这一篇就够了
Concurrent database access 本文译自:https://dmytrodanylyk.com/articles/concurrent-database/ 对于 Android D ...
- 「Django」rest_framework学习系列-API访问跨域问题
#以中间件方式解决API数据访问跨域问题1.API下新建文件夹下写PY文件a.引入内置类继承: from django.middleware.common import MiddlewareMixin ...
- 数据库访问优化之四:减少数据库服务器CPU运算
1.使用绑定变量 绑定变量是指SQL中对变化的值采用变量参数的形式提交,而不是在SQL中直接拼写对应的值. 非绑定变量写法:Select * from employee where id=123456 ...
- 「Django」contenttypes基本用法
当一张表和多个表ForeignKey关联,并且多个FK中只能选择其中一个或其中n个时,可以利用contenttypes,只需定义三个字段就搞定! contenttypes 是Django内置的一个应用 ...
- 「Django」rest_framework学习系列-序列化
序列化方式一 :在业务类里序列化数据库数据 class RolesView(APIView): def get(self,request,*args,**kwargs): roles = models ...
- 「Django」学习之路,持续更改
一.setting设置 1.设置 局域网可以部署连接 ALLOWED_HOSTS = ['*.besttome.com','192.168.1.100'] 2.static配置 STATIC_URL ...
- 「Django」与mysql8连接的若干问题
1.setting配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', #数据库驱动名 'NAME': 'my_tes ...
随机推荐
- 在intelij IDEA中添加对jetBrick文件的识别
在intelij IDEA中添加对jetBrick文件的识别 打开setting, 搜索File Types, 在Recognized File Types窗口找到Java Server Page或者 ...
- 第五周PSP作业
PSP表格: 累积进度条: 折线图: 饼状图:
- 进阶系列(9)——linq
一.揭开linq的神秘面纱(一)概述 LINQ的全称是Language Integrated Query,中文译成“语言集成查询”.LINQ作为一种查询技术,首先要解决数据源的封装,大致使用了三大组 ...
- 【线段树求区间第一个不大于val的值】Lpl and Energy-saving Lamps
https://nanti.jisuanke.com/t/30996 线段树维护区间最小值,查询的时候优先向左走,如果左边已经找到了,就不用再往右了. 一个房间装满则把权值标记为INF,模拟一遍,注意 ...
- FivePlus——成果展示
思路描述:描述对于自己此次任务是如何思考的 这次作业没能帮上什么忙,刚开始还对这次作业有所期待,然而,第一次听他们讨论的时候就??? 之后又去查了一些诸如贪吃蛇类的小游戏,知道大概可以达成什么效果,但 ...
- SELECT - OVER 子句 (Transact-SQL)
标题:SELECT - OVER 子句 (Transact-SQL) 地址:https://docs.microsoft.com/zh-cn/sql/t-sql/queries/select-over ...
- C#中委托的理解
请注意,这只是个人关于C#中委托的一点点理解,参考了一些博客,如有不周之处,请指出,谢谢! 委托是一种函数指针,委托是方法的抽象,方法是委托的实例.委托是C#语言的一道坎,明白了委托才能算是C#真正入 ...
- 奇异值分解(SVD)原理详解及推导 (转载)
转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/43053513 在网上看到有很多文章介绍SVD的,讲的也都不错,但是感觉还是有 ...
- The goal you specified requires a project to execute but there is no POM in this directory
[INFO] Scanning for projects... [INFO] ------------------------------------------------------------- ...
- Hibernate 中 load() 方法导致的 noSession 异常
之所以要写这个,是因为最近碰到了一个延迟加载的 load() 导致出现 noSession 的异常. 下面第三种方式解决这个问题需要用到一个本地线程的对象,也就是 ThreadLocal 类,之前写过 ...