前言 开发中的大部分功能本质都是对数据库的操作,之前一直遇到 nyconnnections 这个错误估计也是代码质量不高导致, 所以对数据库的重复操作次数以及连接数太多导致的。自己通过django对数据库的操作主要是通过ORM,所以掌握关于ORM能够减少查询的tips确实能够帮助提升很大一部分性能。 在这里赞美GPT,给了非常多的优化方案
关于查询
当你查询单个主对象或主对象列表并需要在模板 或其它地方中使用到每个对象的关联对象信息时,请一定记住使用select_related和prefetch_related一次性获取所有对象信息,从而提升数据库查询效率,避免重复查询。
这部分的优化场景,主要是针对关联查询的两个方法的示例
#select_related
1 2 3 4 5 6 7 8 9 10 11 12 task_config_item_record = TaskConfigItem.objects.filter ( task__user=user_id, task__status=1 , task__show_dashboard=1 ).select_related('template' , 'task_config' ) url_list = [ { "task_name" : tci.task.name, tci.template.name: get_payload(tci.task_config.key, tci.template.payload) } for tci in task_config_item_record[:30 ] ]
当在一对一或者一对多的使用场景中,可以用select_related来一次性获取住对象及相关对象的信息 会有一些其他的使用情况 #selected_related_demo
对于多对多的数据表关联信息查询一般不回用select_related方法,这样做是避免做JOIN操作造成最后的表非常大
1 2 3 4 5 # 文章列表及每篇文章的tags对象名字信息 Article.objects.all().prefetch_related('tags__name') # 获取id=13的文章对象同时,获取其相关tags信息 Article.objects.prefetch_related('tags').get(id=13)
其他利用场景 #prefetch_related_demo
in_bulk #in_bulk in_bulk() 接受一个 id 值列表并返回一个字典,将每个 id 映射到具有该 id 的对象实例。如果不将列表传递给 in_bulk() 方法,则将返回所有对象。 假设我只想检索 id 为 1 和 4 的学生,我可以这样做。
1 2 3 4 5 6 >>> students = Student.objects.in_bulk([1, 4]) >>> students[1].name 'Regina Johnson' >>> students[4].name 'Jessie Smith'
关于聚合查询 annotate #annotate 主要用于跨表查询,作用于queryset,其实就是给Queryset添加一个属性,Queryset中的每个对象都会有那么一个属性,属性可以是模型中的字段 例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Brand (models.Model): name = models.CharField(max_length=20 , unique=True , verbose_name='名称' ) location = models.CharField(max_length=30 , default='中国' , blank=True , verbose_name='品牌所属地区' ) logo = models.ImageField(null=True , blank=True , verbose_name='Logo图片' ) create_time = models.DateField(auto_now_add=True , verbose_name='创建时间' ) class Goods (models.Model): name = models.CharField(max_length=50 , verbose_name='名称' ) sales = models.IntegerField(default=0 , null=True , blank=True , verbose_name='销量' ) comments = models.IntegerField(default=0 , null=True , blank=True , verbose_name='评价数' ) brand = models.ForeignKey(Brand, null=True , on_delete=models.PROTECT, verbose_name='所属品牌' ) create_time = models.DateField(auto_now_add=True , verbose_name='创建日期' )
商品模型中 brand
字段就是外键,关联的品牌Brand
当我想查询每个品牌下对应商品的数量时,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 result = Brand.objects.annotate(num_count=Count('goods' )) result[0 ].num_count result = Brand.objects.values('name' ).annotate(num_count=Count('goods' )) < QuerySet[{'name' : 'oppo' , 'num_count' : 0 }, {'name' : '华为' , 'num_count' : 3 }, {'name' : '小米' , 'num_count' : 0 }, { 'name' : '苹果' , 'num_count' : 3 }] > SELECT `tb_brand`.`name`, COUNT(`tb_goods`.`id `) AS`num_count` FROM `tb_brand` LEFT OUTER JOIN `tb_goods` ON(`tb_brand`.`id ` = `tb_goods`.`brand_id`) GROUP BY `tb_brand`.`name` ORDER BY NULL result = Brand.objects.values('name' ).annotate(sale_count=Sum('goods__sales' )) —
关于Antenna中的聚合查询
1 2 message_date_count = message_record.annotate(date=TruncDate('create_time' )).values('date' ). annotate(count=Count('id' )).order_by('date' )
使用annotate()方法计算每天消息的数量,使用values()方法来指定查询的字段,使用TruncDate()[[TruncDate]] 方法来截取日期,使用Count()方法进行聚合计算,最后使用order_by()方法排序。
aggregate #aggregate 也是作用于queryset,对某列进行如下操作:
Sum:求和
Count:数量
Avg:平均
Max:求最大值
Min:求最小值
Variance:计算方差
StdDev:计算标准差
1 2 3 4 result = Goods.objects.all ().aggregate(avg=Avg('sales' ), sum =Sum('sales' )) {'avg' : 3.625 , 'sum' : 29 }
关于创建 对于for循环的数据对象进行保存,那就会导致每次循环都要保存一次数据,这样会增加开销,例如
1 2 3 4 5 6 7 8 9 10 11 12 if nums.isdigit() and int (nums) > 0 : for i in range (int (nums)): device = Device( category=category, seat=seat_obj, asset_code='' , asset_num='V{}-{}' .format (category.name, str (i).zfill(4 )), use_info='' , operator=operator, op_type=1 ) device.save()
针对于这种情况我们选择先批量创建对象,最后使用bulk_create一次保存到数据库 #bulk_create
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 device_obj_list = [] for i in range (int (nums)): device_obj_list.append( Device( category=category, seat=seat_obj, asset_code='---' , asset_num='{}-xxxx' .format (category.name), use_info='---' , operator=operator, op_type=1 ) ) Device.objects.bulk_create(device_obj_list) messages.info(request, '批量添加{}条数据完成!' .format (nums))