一、分析请求慢响应的主要原因

将请求执行的任务按功能分为几块,用time.time()打印每个模块的执行时间,大部分情况下性能会主要消耗在某一个模块上,即80%的性能问题是出在20%的代码上

找到主要原因后,就专注于优化这一个模块

二、使用django.db.connection.queries查看某个请求的sql执行情况

from django.db import connection
...
print(connection.queries)
# [{'sql':--执行的sql语句--, 'time':--sql语句执行的时间--}...]

注意只有在debug=True模式下才能获取connection.queries

多数据库

db.connections是一个类似字典的对象,可以通过某个数据库连接的别名获取这个数据源的connection。比如connections['my_db_alias']

from django.db import connections
for key in connections:
print(key)
# 可以打印出所有配置了的数据源别名,django会为每个数据源创建一个connection

通过django/db/init.py中

class DefaultConnectionProxy:
"""
Proxy for accessing the default DatabaseWrapper object's attributes. If you
need to access the DatabaseWrapper object itself, use
connections[DEFAULT_DB_ALIAS] instead.
"""
def __getattr__(self, item):
return getattr(connections[DEFAULT_DB_ALIAS], item) def __setattr__(self, name, value):
return setattr(connections[DEFAULT_DB_ALIAS], name, value) def __delattr__(self, name):
return delattr(connections[DEFAULT_DB_ALIAS], name) def __eq__(self, other):
return connections[DEFAULT_DB_ALIAS] == other connection = DefaultConnectionProxy()

由于DEFAULT_DB_ALIAS='default',可以知道from django.db import connection获取的就是connections['default']

因此,在多数据库的情况下,可以通过connections获取特定数据库连接的queries或cursor

from django.db import connections
connections['my_db_alias'].queries
cursor = connections['my_db_alias'].cursor()

输出总的sql执行时间

sql_time = 0.0
for q in connections['my_db_alias'].queries:
sql_time += float(q['time'])
print('sql_time', sql_time)

三、各种update写法的执行速度

数据库数据量为60w

以下sql执行时间都是在update有实际数据的更新时记录的,如果update没有实际更新,sql执行时间会大幅缩减。

1、使用raw_sql自定义查询

cursor = connections['my_db_alias'].cursor()
# 实例化cursor的时间不计入
cursor.execute("update item set result=%s, modified_time=Now() where id=%s", (result, 10000))
print(time()-start)
print(connections['my_db_alias'].queries)
# 0.004s左右,与sql执行时间相同

2、使用ORM的update方法

Item.objects.using('my_db_alias').filter(id=10000).update(result=result)
# 0.008s左右,sql执行时间是0.004s

3、使用object.save ()方法

item = Item.objects.using('my_db_alias').filter(id=10000).first()
item.result = result
item.save(using='my_db_alias')
# 0.012s左右,sql执行时间是0.004s

因此,执行update的效率raw_sql>update方法>save()方法

四、使用prefetch_related减少数据库查询

prefetch_related对关系使用独立的query,即先查出符合过滤条件的表A的id,再用这些id去查表B,并且在python中将两批数据关联。

假设我们有一个博客应用,有Blog、Comment两张表,一条博客可以有多个关联的评论:

from django.db import models

class Blog(models.Model):
name = models.CharField(max_length=255)
author = models.CharField(max_length=100)
content = models.TextField() class Comment(models.Model):
author = models.CharField(max_length=100)
content = models.TextField()
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='comments')

现在有一个需求,找出所有名为“Django教程”的博客下的评论内容。

用这个例子可以看到使用prefetch_related是如何减少数据库查询的。

不使用prefetch_related:

def test_prefetch_related():
blogs = Blog.objects.filter(name="Django教程")
for blog in blogs:
comments = Comment.objects.filter(blog_id=blog.id)
for comment in comments:
print(comment.content)
print(len(blogs)) # 34
print(len(connection.queries)) # 39

匹配指定名称的博客有34个,可以看到获取每个博客评论的时候,都查了一次Comment表,总共查询了34次Comment表,效率是非常低的。我们的目标应该是查询一次Blog表、查询一次Comment表即获得所需的数据

使用prefetch_related:

def test_prefetch_related():
blogs = Blog.objects.filter(name="Django教程").prefetch_related('comments')
for blog in blogs:
for comment in blog.comments.all():
print(comment.content)
print(len(blogs)) # 34
print(len(connection.queries)) # 6
for query in connection.queries:
print(query)

发起的sql数量由39个减到6个

具体的:

{'sql': 'SELECT @@SQL_AUTO_IS_NULL', 'time': '0.000'}
{'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}
{'sql': 'SELECT VERSION()', 'time': '0.000'}
{'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'} # 找到所有符合过滤条件的博客文章
{'sql': "SELECT `blog`.`id`, `blog`.`name`, `blog`.`author`, `blog`.`content` FROM `blog` WHERE `blog`.`name` = 'Django教程'", 'time': '0.014'} # 根据上面找到的博客文章id去找到对应的评论
{'sql': 'SELECT `comment`.`id`, `comment`.`author`, `comment`.`content`, `comment`.`blog_id` FROM `comment` WHERE `comment`.`blog_id` IN (5160, 1307, 2984, 5147, 5148, 3062, 5148, 5161, 2038, 1923, 2103, 3014, 1466, 2321, 5166, 5154, 1980, 3550, 3542, 5167, 2077, 2992, 3209, 5168, 8855, 1163, 368, 174, 3180, 5168, 8865, 2641, 3224, 4094)', 'time': '0.007'}

与我们的目标相符

何时prefetch_related缓存的数据会被忽略

要注意的是,在使用QuerySet的时候,一旦在链式操作中改变了数据库请求,之前用prefetch_related缓存的数据将会被忽略掉。这会导致Django重新请求数据库来获得相应的数据,从而造成性能问题。这里提到的改变数据库请求指各种filter()、exclude()等等最终会改变SQL代码的操作。

prefetch_related('comments')隐含表示blog.comments.all(),因此all()并不会改变最终的数据库请求,因此是不会导致重新请求数据库的。

然而

for comment in blog.comments.filter(author="jack"):

就会导致Django重新请求数据库

只需要取出部分字段

博客文章的content字段数据量可能非常大,取出而不用可能会影响性能。之前的需求中可以进一步优化只取出博客和评论中的部分字段

blogs = Blog.objects.filter(name="Django教程").only('id').\
prefetch_related(
Prefetch('comments', queryset=Comment.objects.only('id', 'content', 'blog_id'))
)

使用only指定查询的字段,使用Prefetch对象自定义prefetch_related查询的内容(默认queryset=Comment.objects.all()

注意comment.blog_id字段是必须要取出的,因为在python中将comments拼到对应的blog时需要comment.blog_id字段与blog.id字段匹配,如果在Prefetch对象中不取出comment.blog_id,拼接时会浪费很多数据库查询去找comment.blog_id字段

多数据库的情况

在多数据库的情况下,prefetch_related使用的数据源与主查询指定的数据源一致。

比如:

blogs = Blog.objects.using('my_db_alias').filter(name="Django教程").only('id').\
prefetch_related(
Prefetch('comments', queryset=Comment.objects.only('id', 'content', 'blog_id'))
)

查询Comment表时会使用与Blog一样的数据源

五、向数据库插入数据的时候尽量使用bulk_create

# 以下代码会发起10次数据库插入:
for i in range(10):
Comment.objects.create(content=str(i), author="kim", blog_id=1) # 以下代码只会发起一次数据库插入:
comments = []
for i in range(10):
comments.append(Comment(content=str(i), author="kim", blog_id=1))
Comment.objects.bulk_create(comments, batch_size=5000)

注意:

  1. bulk_create不会返回id:When you bulk insert you don't get the primary keys back

  2. 小心数据库连接超时:如果一次性插入过多的数据会导致Mysql has gone away的报错。指定batch_size=5000可以避免这个问题,当插入数据>5000时,会分成多个sql执行数据批量插入

六、尽量不要重复取数据

可以将数据库的数据以id为key存到内存的字典中,这样下次用到的时候就无需再次访问数据库,可提高效率

Django ORM调优实践的更多相关文章

  1. elasticsearch5.3.0 bulk index 性能调优实践

    elasticsearch5.3.0 bulk index 性能调优实践 通俗易懂

  2. JVM性能调优实践——JVM篇

    前言 在遇到实际性能问题时,除了关注系统性能指标.还要结合应用程序的系统的日志.堆栈信息.GClog.threaddump等数据进行问题分析和定位.关于性能指标分析可以参考前一篇JVM性能调优实践-- ...

  3. [转载]Java 应用性能调优实践

    Java 应用性能调优实践 Java 应用性能优化是一个老生常谈的话题,笔者根据个人经验,将 Java 性能优化分为 4 个层级:应用层.数据库层.框架层.JVM 层.通过介绍 Java 性能诊断工具 ...

  4. 软件性能测试分析与调优实践之路-Web中间件的性能分析与调优总结

    本文主要阐述软件性能测试中的一些调优思想和技术,节选自作者新书<软件性能测试分析与调优实践之路>部分章节归纳. 在国内互联网公司中,Web中间件用的最多的就是Apache和Nginx这两款 ...

  5. PB 级大规模 Elasticsearch 集群运维与调优实践

    PB 级大规模 Elasticsearch 集群运维与调优实践 https://mp.weixin.qq.com/s/PDyHT9IuRij20JBgbPTjFA | 导语 腾讯云 Elasticse ...

  6. MindSpore模型精度调优实践

    MindSpore模型精度调优实践 引论:在模型的开发过程中,精度达不到预期常常让人头疼.为了帮助用户解决模型调试调优的问题,为MindSpore量身定做了可视化调试调优组件:MindInsight. ...

  7. 软件性能测试分析与调优实践之路-Java应用程序的性能分析与调优-手稿节选

    Java编程语言自从诞生起,就成为了一门非常流行的编程语言,覆盖了互联网.安卓应用.后端应用.大数据等很多技术领域,因此Java应用程序的性能分析和调优也是一门非常重要的课题.Java应用程序的性能直 ...

  8. 软件性能测试分析与调优实践之路-JMeter对RPC服务的性能压测分析与调优-手稿节选

    一.JMeter 如何通过自定义Sample来压测RPC服务 RPC(Remote Procedure Call)俗称远程过程调用,是常用的一种高效的服务调用方式,也是性能压测时经常遇到的一种服务调用 ...

  9. MySQL数据库的性能分析 ---图书《软件性能测试分析与调优实践之路》-手稿节选

    1  .MySQL数据库的性能监控 1.1.如何查看MySQL数据库的连接数 连接数是指用户已经创建多少个连接,也就是MySQL中通过执行 SHOW  PROCESSLIST命令输出结果中运行着的线程 ...

随机推荐

  1. EC Round 41 (Rated for Div. 2)主席树 E. Tufurama

    简单分析一下,对于x<y,求a[x]>=y 同时a[y]>=x 再简化一下,求1-a[y]区间内大于>=y的个数...主席树牛逼 #include<iostream> ...

  2. iptables 累计(Accounting)

    对於每一条规则,核心各自设置两个专属的计数器,用于累计符合该条件的封包数,以及这些封包的总位元组数.这两项资讯可用於统计网路用量. 举例来說,假设有一台Internet闸道器路,eth0接内部网络,e ...

  3. 洛谷P4018 Roy&October之取石子 题解 博弈论

    题目链接:https://www.luogu.org/problem/P4018 首先碰到这道题目还是没有思路,于是寻思还是枚举找一找规律. 然后写了一下代码: #include <bits/s ...

  4. [转载] CentOS系统开机自动挂载光驱 和 fstab文件详解

    参考 http://blog.itpub.net/12272958/viewspace-676977/ 一.开机自动挂载光驱 1.按习惯,root用户,在/media目录下建立目录cdrom——mkd ...

  5. Flex AIR应用GPS定位功能(Android和IOS)

    说明: 使用AIR进行GPS定位功能实现时,会经常判断GPS是否打开.一般的官方或者书上的介绍的方法,测试后,只能对Android系统进行判断,而对ios系统则无法进行判断. 经过研究测试,终于解决实 ...

  6. tf.contrib.layers.xavier_initializer

    https://blog.csdn.net/yinruiyang94/article/details/78354257xavier_initializer( uniform=True, seed=No ...

  7. H3C 配置基本ACL

  8. 【t093】外星密码

    Time Limit: 1 second Memory Limit: 128 MB [问题描述] 有了防护伞,并不能完全避免2012的灾难.地球防卫小队决定去求助外星种族的帮助.经过很长时间的努力,小 ...

  9. 2019-8-31-C#-获取-PC-序列号

    title author date CreateTime categories C# 获取 PC 序列号 lindexi 2019-08-31 16:55:58 +0800 2018-7-30 10: ...

  10. H3C进入目录---用户视图

    进入logfile文件目录 <H3C>cd logfile/        //进入目录 <H3C>pwd             //显示当前所属的目录 cf:/logfil ...