最近几天研究了一下django ORM查询优化,响应时间慢问题,主要原因还是代码规范和方法使用不当,如果正确使用相应方法,大部分的性能问题都是可以解决,记录如下:
Django QuerySet懒执行
只有访问到对应的数据时,才会访问数据库,如果再次读取查询到的数据时,不会触发访问数据库操作,返回的是QuerySet、ValuesQuerySet、ValuesListQuerySet、Model实例
会执行数据库操作的操作有:
- Iteration,对Queryset进行迭代操作
- slicing分片, 如queryset[:5]
- 序列化Pickling
- repr()/str()将对象转为字符串
- len()/list()/bool()/print()操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from django.db import connection users = User.objects.all() users = User.objects.all().select_related('group') for user in users: print(user.name) print(user.group.name) l = connection.queries print(len(l), l) users = User.objects.filter(age=25) list(users) or if users: pass user = users[0]
|
Model中一般会定义外键关联,查询如果编写不当,会多次访问数据库查询,影响效率;通过select_related方法来查询外键(ForeignKey)或一对一(OneToOneField)关系,其实就是sql语句中join操作;在后面使用外键关系查询时将不需要执行数据库查询
使用prefetch_related提升关联多对多或多对一查询
prefetch_related 执行一个单独的查找,它允许预先读取多对多和多对一的对象数据,这是 select_related 做不到的。另外 perfetch_related 也可以与通用外键和关系一起使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| DeviceInfo.objects.filter(operator_id='dc8b37483b27402d90a5a28d13ce330c') SELECT `device_info`.`id`, `device_info`.`number`, `device_info`.`type`, `device_info`.`plate_no_id`, `device_info`.`status`, `device_info`.`offline_date`, `device_info`.`operator_id` FROM `device_info` WHERE `device_info`.`operator_id` = dc8b37483b27402d90a5a28d13ce330c DeviceInfo.objects.select_related('operator').filter(operator_id='dc8b37483b27402d90a5a28d13ce330c') SELECT `device_info`.`id`, `device_info`.`number`, `device_info`.`type`, `device_info`.`plate_no_id`, `device_info`.`status`, `device_info`.`offline_date`, `device_info`.`operator_id`, `user`.`password`, `user`.`last_login`, `user`.`is_superuser`, `user`.`id`, `user`.`created_at`, `user`.`is_deleted`, `user`.`mobile_number`, `user`.`is_active`, `user`.`is_staff`, `user`.`is_driver`, `user`.`depgroup_id` FROM `device_info` INNER JOIN `user` ON (`device_info`.`operator_id` = `user`.`id`) WHERE `device_info`.`operator_id` = dc8b37483b27402d90a5a28d13ce330c from django.db import models class City(models.Model): pass class Person(models.Model): hometown = models.ForeignKey(City) class Book(models.Model): author = models.ForeignKey(Person) b = Book.objects.select_related('author__hometown').get(id=4) p = b.author c = p.hometown b = Book.objects.get(id=4) p = b.author c = p.hometown
|
不要查询不需要的值
- 通过values和values_list来限制返回值
- 通过only指定字段和defer排除字段
- 如果只需要id,可以使用queryset.values_list(‘id’, flat=True)
直接使用外键值
如果只想获取外键id,可通过obj_id的方式获取,优先于obj.id;obj.id方式会为子表内容保存额外查询
用count()代替len(), exists()代替if queryset
len()方法相当于会把整个queryset遍历一次,把所有的数据都取出来对象化,消耗大量的资源
对缓存的queryset只进行一次遍历,使用iterator()
1 2 3
| for user in User.objects.all().iterator(): do_something(user)
|
避免多次查询
筛选表中不同条件的数据时,一般采用写多个查询进行筛选,数据多时严重影响性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| users = [ 'ttxsgoto01', 'ttxsgoto02', 'ttxsgoto03', ] from models import User for user in users: user1 = User.objects.filter(username=user, age=21) user2 = User.objects.filter(username=user, age=22) user3 = User.objects.filter(username=user, age=23) user4 = User.objects.filter(username=user, sex='M') print(user1.count(), user2.count(), user3.count(), user4.count()) for user in users: _user = User.objects.filter(username=user).values_list('age', 'sex') user1 = filter(lambda x:True if x[0]==21 else False, _user) user2 = filter(lambda x:True if x[0]==22 else False, _user) user3 = filter(lambda x:True if x[0]==23 else False, _user) user4 = filter(lambda x:True if x[1]=='M' else False, _user) print(user1.count(), user2.count(), user3.count(), user4.count())
|
创建表索引
根据业务需求,创建对应的索引字段
对于复杂的数据库查询操作,使用原生SQL实现
性能分析
方法一: code
1 2
| from django.db import connection dbsql = connection.queries
|
方法二: shell
1 2 3 4 5 6 7
| - pip install django-extensions - INSTALLED_APPS = ( ... 'django_extensions', ... ) - python manage.py shell_plus --print-sql
|
参考文档