前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >翻译了Django1.4数据库访问优化部分

翻译了Django1.4数据库访问优化部分

作者头像
the5fire
发布2019-02-28 17:21:08
6130
发布2019-02-28 17:21:08
举报

Django数据库访问优化

  • by huyang
  • @手机搜狐
  • date 2013-05-08

rst生成的html5在线ppt下载:http://www.kuaipan.cn/file/id_12834302878348970.htm

先做性能分析 - 两个工具

  • django.db.connection
代码语言:javascript
复制
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_toolbarlink

标准的数据库优化技巧

  • Indexes, 分析应该添加什么样的索引,使用 django.db.models.Field.db_index
  • 使用对应的字段类型
代码语言:javascript
复制
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()

获取大量数据时

代码语言:javascript
复制
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语句

代码语言:javascript
复制
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

代码语言:javascript
复制
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
代码语言:javascript
复制
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) 。

代码语言:javascript
复制
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 属性,下面的模板代码是最优的:

代码语言:javascript
复制
{% 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 %}

它是最优的是因为:

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

使用 QuerySet.update() 和 delete()

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

直接使用外键的值

获取频道ID:

代码语言:javascript
复制
news.channel_id

而不是:

代码语言:javascript
复制
news.channel.id

批量插入

  • 用 django.db.models.query.QuerySet.bulk_create() 批量创建对象,减少SQL查询的 数量。比如
代码语言:javascript
复制
Entry.objects.bulk_create([
    Entry(headline="Python 3.0 Released"),
    Entry(headline="Python 3.1 Planned")
])

...而不是

代码语言:javascript
复制
Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")

这同样适用于 ManyToManyFields, 因此,这么做

代码语言:javascript
复制
team.members.add(me, my_friend)

...而不是这么做

代码语言:javascript
复制
team.members.add(me)
team.members.add(my_friend)

...这里 team 和 members 是多对多的关系。

参考资源:

https://docs.djangoproject.com/en/1.4/topics/db/optimization/

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2013-05-09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Django数据库访问优化
  • 先做性能分析 - 两个工具
  • 标准的数据库优化技巧
  • 理解QuerySets
    • 理解QuerySet的求值过程
      • 理解被缓存的属性
        • 注意
      • 使用with模板标签
        • 使用iterator()
        • 让数据库做它自己的工作
          • 基本概念
            • 使用 QuerySet.extra()
              • 使用原生的SQL
              • 预加载数据
              • 不要获取你不需要的数据
                • 使用 QuerySet.values() 和 values_list()
                  • 使用 QuerySet.defer() 和 only()
                    • 使用 QuerySet.count()
                      • 使用 QuerySet.exists()
                        • 不要过度使用 count() 和 exists()
                          • 它是最优的是因为:
                            • 使用 QuerySet.update() 和 delete()
                              • 直接使用外键的值
                              • 批量插入
                              • 参考资源:
                              相关产品与服务
                              数据库
                              云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档