Django ORM调优实践

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

将请求执行的任务按功能分为几块,用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缓存的数据会被忽略

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppjdz.html