记一次djnago orm框架中的数据优化

47 篇文章 0 订阅

1 only和defer优化

有一个表为:
在这里插入图片描述
然后我在djnago中使用orm框架中查询,sql语句为:

obj = AppOauthInfoLog.objects.all().first()
print(obj.id)
print(obj.app_id)

执行的sql结果为:
在这里插入图片描述
根据图片可以看出来,这条sql把表中的所有字段都给查询了出来,这样明显浪费i/o,如果我们使用only来优化那:

obj = AppOauthInfoLog.objects.only("id", "app_id").first()
print(obj.id)
print(obj.app_id)

执行的结果为:
在这里插入图片描述
明显可以看到sql变短了,这样效率就会上去,那如果我多一行打印那:

obj = AppOauthInfoLog.objects.only("id", "app_id").first()
print(obj.id)
print(obj.app_id)
print(obj.read_num)

在这里插入图片描述
也没有报错,但是多查询了一次sql, 所以使用only的时候要注意,没有在only中查询的字段,不要乱用,而defer是排除哪些字段不查,和only的效果是一样的

2. 外键的__查询

有时候表中会有外键关系,这里有一个表任务表tb_task, 还有一张表tb_extend_task,而tb_extend_task中的字段task_id是表tb_task的外键, 那么我们执行下面的sql:

extend_query = ExtendTask.objects.filter(task_id=71).values("task__name", "id")
for i in extend_query:
    print(i['id'])
    print(i['task__name'])

在这里插入图片描述
通过sql我们可以看到,这里使用了连接查询,如果使用单表查询,需要两条sql才可以,这里一条就可以了,那么是否就意味着连接查询就好那,不是这样的,如果项目较小,那么可以这样做,但是在高并发并且表中数据较多的情况下,不要使用连接查询,有以下原因:
1. 让缓存的效率更高。许多应用程序可以方便地缓存单表查询对应的结果对象。另外对于MySQL的查询缓存来说,如果关联中的某个表发生了变化,那么就无法使用查询缓存了,而拆分后,如果某个表很少改变,那么基于该表的查询就可以重复利用查询缓存结果了。

2. 将查询分解后,执行单个查询可以减少锁的竞争。

3. 在应用层做关联,可以更容易对数据库进行拆分,更容易做到高性能和可扩展。

4. 查询本身效率也可能会有所提升。

5. 可以减少冗余记录的查询。

6. 更进一步,这样做相当于在应用中实现了哈希关联,而不是使用MySQL的嵌套环关联,某些场景哈希关联的效率更高很多。

7. 单表查询有利于后期数据量大了分库分表,如果联合查询的话,一旦分库,原来的sql都需要改动。

8. 上次看到某个CTO技术分享,公司规定底层禁止用join联合查询。数据大的时候确实慢。

9. 联合查询或许确实快,但是mysql的资源通常比程序代码的资源紧张的多。

如果我们执行下面的sql那:

extend_query = ExtendTask.objects.filter(task_id=71).select_related("task").values("task__name", "id")

会发现和上面的结果是一样的,这里不在截图展示

那如果是这样的那?

extend_query = ExtendTask.objects.filter(task_id=71)
for i in extend_query:
    print(i.task.name)

在这里插入图片描述
我们可以看到,tb_extend_task被查询了一次,而tb_task有三条数据,就被查询了三次,这就是N+1问题,就是我明明执行了一条sql,但是主键表中有几条命中的数据,就会被查询几次,如何解决使用select_related()将其变为连接查询:

extend_query = ExtendTask.objects.filter(task_id=71).select_related("task")
for i in extend_query:
    print(i.task.name)

在这里插入图片描述
这样就会变成了一条sql,大大的缓解了数据库压力,还有一种办法就是,使用单表查询,两次查询,自然也会解决这个问题, 而prefetch_related的效果就是将其变为了两次查询:

extend_query = ExtendTask.objects.filter(task_id=71).prefetch_related("task")
for i in extend_query:
    print(i.task.name)

在这里插入图片描述
那么prefetch_related和select_related的区别不止是这样的,select_related只适用于一对多,就是从有外键的一方开始查询,而prefetch_related不止适用于一对多,还适用于多对一,就是从主键的哪一方开始查询:

extend_query = Task.objects.filter(id=71)
for i in extend_query:
    print(i.extendtask_set.all())  # 关联表表名小写_set

在这里插入图片描述
这里我们可以看到先查询了tb_task表,然后查询了tb_extend_task表,查询了两次, 相当于单表查询,那如果使用prefetch_related那?其效果是和上面是一样的

extend_query = Task.objects.filter(id=71).prefetch_related("extendtask_set")
for i in extend_query:
    print(i.extendtask_set.all())

那prefetch_related在序列化器中如何使用那, Fabrics是主表, Fabricsspuimage表和Recommendfabric表是外键表:

queryset = Fabrics.objects.filter(q_filter, **conn).select_related('store').prefetch_related('recommendfabric_set__recommend_model', 'fabricsspuimage_set').order_by('-id')

序列化器中:

spu_image_list = SpuImageSerializer(source='fabricsspuimage_set', read_only=True, many=True)

class SpuImageSerializer(serializers.ModelSerializer):
    """创建spu时一起使用"""
    image = serializers.SerializerMethodField()

    class Meta:
        model = FabricsSPUImage
        fields = ('image',)

    def get_image(self, obj):
        return get_file_url(obj.image if obj.image else '')

总结:总的来说,如果项目较小,表数据不大,并发量也不是很高,建议多使用连接查询,如果并发量大,表数据量的大,还是使用单表查询,如果使用连接查询较多,增加锁的竞争,就容易将数据库cpu打满,造成数据库挂掉, 注意:读锁会阻塞写但是不会阻塞读,写锁则会把读和写都阻塞

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值