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

将请求执行的任务按功能分为几块,用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. mysql 字段名和关键字冲突

    用"(`)"将有冲突的字段框起来,,键盘上1边上那个键. 例: SELECT * FROM yun_roleright WHERE right LIKE '%{13}%'; 上面s ...

  2. Laravel 5.3 用户验证源码探究 (一) 路由与注册

    https://blog.csdn.net/realghost/article/details/52558962 简介 Laravel 从 5.2 开始就有了开箱即用的用户验证,5.3 又在 5.2 ...

  3. 杂项-Java-百科:war-un

    ylbtech-杂项-Java-百科:war-un 1.返回顶部 1. war是一个可以直接运行的web模块,通常用于网站,打成包部署到容器中.以Tomcat来说,将war包放置在其\webapps\ ...

  4. 随机线性网络编码的C语言实现,实现可靠传输:实现篇(2)

    伽罗华域(2^8)乘除法的编程实现

  5. ]ubuntu开机自动挂载的ntfs硬盘的权限问题

    原文地址:ubuntu开机自动挂载的ntfs硬盘的权限问题 在linux操作系统中, 挂载是一个非常重要的功能,使用非常频繁. 它指将一个设备(通常是存储设备)挂接到一个已存在的目录上. (这个目录可 ...

  6. ThinkPHP商城实战

    ThinkPHP3.2.3商城实战教程,需要的联系我,QQ:1844912514 千万级php电商秒杀项目实战  ,需要的联系我,QQ:1844912514

  7. Vue进阶

    组件深入 过渡&动画 可复用性&组合 工具&规模化&内在 ****************参考*************** vue官方教程

  8. git的安装与命令行基本的使用

    1.https://git-scm.com/ 点击这个网址进入git的官方网站 2,.进去里面会有提示,64位于32位的,根据自己的电脑安装 3 下载完了过后就直接安装,一般会安装在c盘里面 ,进入安 ...

  9. Native memory allocation (mmap) failed to map xxx bytes for committing reserved memory

    遇到问题 在服务器上运行 nexus 出现Native memory allocation (mmap) failed to map 838860800 bytes for committing re ...

  10. 陈志生:德国信贷工厂风控模式对P2P的启发

    上海合盘金融信息服务股份有限公司董事长陈志生 和讯银行消息 "2014中国金融论坛"于5月14-15日在北京召开,本次论坛主题为“全面深化金融体制改革与实体经济增长”.和讯网作为指 ...