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打满,造成数据库挂掉, 注意:读锁会阻塞写但是不会阻塞读,写锁则会把读和写都阻塞