使用django了解n+1查询的问题
#django #database #orm #djangorestframework

概括

  • 问题n+1查询
  • 了解Django中的问题情况
  • criaabo£o da api com drf
  • 使用padrã£o
  • 的djangodispísta进行解决
  • 通过获得或Django虚拟模型
  • 参考

介绍什么问题n+1查询

问题n+1查询是应用程序中实体之间存在某些关联的问题,因此,当有必要从A中访问信息时,请为每个实体提供许多要求,以使每个实体都有很多要求应用程序在生产中时,应用程序缓慢,可能会花更多的钱。

了解Django中的问题

为了理解n+1查询的问题,让我们去示例,想象一个平台使用帖子视频的应用程序,并且这些视频具有某些类别,用户路径可以添加,以便视频进入喜欢这些类别的人。通过这种方式,我们可以拥有以下图表实体关系:

der

我们可以考虑第一个关系,用户发布许多视频,而视频是用户,我们对许多人有关系1。想象一下,我们在整个平台上有3个常规(现在开始),每个平台都发布了1个视频(一个帖子母亲),并且我们的API路线中有一条路线,向我们展示了从常规和常规和视频的标题中显示的信息已发布,需要1个查询,以捕获所有用户和N Dakes,以获取与每个用户相关的视频中的信息。所以我们有:

SELECT * FROM User ...
SELECT title FROM Video WHERE userID = 1
SELECT title FROM Video WHERE userID = 2
SELECT title FROM Video WHERE userID = 3

当我们增加常规数量时,就会发生大问题。关于第二个关联,想法是相同的。因此,我们已经可以想象解决问题的方法,我需要进行查询以获取所有常规和另一个问题,以获取所有常规的vides

SELECT * FROM User ...
SELECT title FROM Video WHERE userID in (1,2,3)

但是出现了问题,您如何使用Django Orm?

使用DRF创建API

pron© - 条件

  • 对Django操作的基本理解
  • 对Django REST框架操作的基本理解

mass

创建一个名为 video_platform 的文件夹,并安装虚拟环境以不污染您的环境。

python -m venv venv

安装依赖:

pip install django djangorestframework

创建项目和应用程序:

django-admin startproject video_plataform .
django-admin startapp core

设置

随后,让我们添加到settings.py文件两个应用程序

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    "core.apps.CoreConfig", #<- new
    "rest_framework" #<- new
]

楷模

为了制作映射,我们可以在model.py文件中添加以下代码并迁移到数据库:

from django.db import models


class User(models.Model):
    name = models.CharField(max_length=30)

    def __str__(self) -> str:
        return self.name

class Video(models.Model):
    title = models.CharField(max_length=30)
    url = models.CharField(max_length=255)
    visualizations = models.IntegerField(default=0)
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="videos")

    def __str__(self) -> str:
        return self.title

class Category(models.Model):
    title = models.CharField(max_length=30)
    videos = models.ManyToManyField(Video, related_name="categories")

    def __str__(self) -> str:
        return self.title
python manage.py makemigrations
python manage.py migrate

填充数据库

创建一个名为“固定装置”的董事

[   {
    "model": "core.user",
    "pk": 1,
    "fields": {
        "name": "Paul"
        }
    },{
        "model": "core.video",
        "pk": 1,
        "fields": {
            "title": "Dancinha do loirinho",
            "url":"www.google.com.br",
            "visualizations": 0,
            "user": 1
        }
    },{
    "model": "core.user",
    "pk": 2,
    "fields": {
        "name": "Marie"
        }
    },{
        "model": "core.video",
        "pk": 2,
        "fields": {
            "title": "Dancinha do forró",
            "url":"www.google.com.br",
            "visualizations": 0,
            "user": 2
        }
    },{
    "model": "core.user",
    "pk": 3,
    "fields": {
        "name": "Mary"
        }
    },{
        "model": "core.video",
        "pk": 3,
        "fields": {
            "title": "The office",
            "url":"www.google.com.br",
            "visualizations": 0,
            "user": 3
        }
    },{
    "model": "core.category",
    "pk": 1,
    "fields": {
        "title": "Entretenimento",
        "videos":[1,2,3]
        }
    },{
    "model": "core.category",
    "pk": 2,
    "fields": {
        "title": "Forró",
        "videos":[2]
        }
    },{
    "model": "core.category",
    "pk": 3,
    "fields": {
        "title": "Funk",
        "videos":[1]
        }}]

在终端运行命令:

python manage.py loaddata data.json

序列化器

创建 serializers.py 文件并放置遏制:

from rest_framework import serializers
from core.models import User, Video, Category

class VideoSerializer(serializers.ModelSerializer):

    class Meta:
        model = Video
        fields = ("title","url","visualizations")
class UserSerializer(serializers.ModelSerializer):
    videos = VideoSerializer(many=True)
    class Meta:
        model = User
        fields = ("name", "videos")

class CategorySerializer(serializers.ModelSerializer):
    videos = VideoSerializer(many=True)

    class Meta:
        model = Category
        fields = ("title")

URL

在项目文件夹中的urls.py文件中添加以下命令:

from django.contrib import admin
from django.urls import path
from core.views import UserListAPIView #<- new

urlpatterns = [
    path('admin/', admin.site.urls),
    path('users/', UserListAPIView.as_view()) #<- new
]

看法

为了能够列出我们的应用程序的用户用途,我们将创建视图 useristapiview

from rest_framework.generics import ListAPIView
from core.serializers import UserSerializer
from core.models import User
from django.db import connection, reset_queries
class UserListAPIView(ListAPIView):
    serializer_class = UserSerializer
    def get_queryset(self):
        users = User.objects.all()

        for query in connection.queries:
            print("Query:", query)

        reset_queries()

        return users

的方式有些好奇,我们有一个从listApiview继承的类,因为我希望它以序列化的方式执行特定资源的所有冲动的清单功能,因此我做o user.objects.all.abignts.all.abect.all.a )命令,但是Django使我们能够使用连接属性知道对银行制作了什么Dakes。BR>

Query: {'sql': 'SELECT "core_user"."id", "core_user"."name" FROM "core_user"', 'time': '0.004'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" = 1', 'time': '0.003'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" = 2', 'time': '0.000'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" = 3', 'time': '0.000'}

如果您记得与我之前提出的相同的疑问,也就是说,我们可以在这里看到问题,如果通常的数量增加,最大的是为此观点捕捉视频的宠儿数量。

使用Django具有标准的解决方案解决

要解决此问题,我们将修改使用ORM的方式,因此请修改:

#users = User.objects.all()
#=>
users = User.objects.prefetch_related("videos")

引起我们正在进行反向访问,因为那些具有外键的人是视频和用户模型,我们必须使用preapeab_realated()进行2个查询。而且它可以通过python,通过python,我们还拥有执行相同功能的select_recyated(),但应在具有外键并通过数据库进行连接的模型中使用,因此仅执行1个查询。因此,如果我们访问路线并前往终端,我们将看到类似的东西:

Query: {'sql': 'SELECT "core_user"."id", "core_user"."name" FROM "core_user"', 'time': '0.004'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" IN (1, 2, 3)', 'time': '0.003'}

正如我们在查询中可以看到的,即使系统有100万个常规,只有2个申请数据库,而不是100万请求1加1。

即将

让我们在显示的用户数据中添加一个新数据,我们可以显示每个用户拥有的 videos 的数量,我们可以添加以下IDE在 serializ.py文件中:

class UserSerializer(serializers.ModelSerializer):
    videos = VideoSerializer(many=True)
    amount_videos = serializers.SerializerMethodField() #<-new
    class Meta:
        model = User
        fields = ("name", "videos", "amount_videos")   #<-new

    def get_amount_videos(self, obj):                  #<-new
        return Video.objects.filter(user=obj).count()  #<-new

以上将创建数据中的新密钥,并以每种常规的视频数量发送给客户,我们可以看到终端所做的daries:

Query: {'sql': 'SELECT "core_user"."id", "core_user"."name" FROM "core_user"', 'time': '0.002'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" IN (1, 2, 3)', 'time': '0.001'}

Query: {'sql': 'SELECT COUNT(*) AS "__count" FROM "core_video" WHERE "core_video"."user_id" = 1', 'time': '0.001'}
Query: {'sql': 'SELECT COUNT(*) AS "__count" FROM "core_video" WHERE "core_video"."user_id" = 2', 'time': '0.000'}
Query: {'sql': 'SELECT COUNT(*) AS "__count" FROM "core_video" WHERE "core_video"."user_id" = 3', 'time': '0.000'}

看,我们的两个查询仍然存在(一切都很好!),我们避免了n+1查询的问题,但是要知道每个常规的vade量仍然存在。>>>>>>>。

了解Django虚拟模型

为了解决提出的问题,其中一种形式是使用一个名为Django虚拟模型的库,此库为我们提供了一种解决n+1查询问题的方法,但仍然可以用高收获性。

安装库:

pip install django-virtual-model

在应用程序中创建一个名为virtual_models.py的文件,如下:

import django_virtual_models as v
from core.models import User, Video
from django.db.models import Count
class VirtualVideo(v.VirtualModel):
    class Meta:
        model = Video
class VirtualUser(v.VirtualModel):
    videos = VirtualVideo(manager=Video.objects)

    amount_videos = v.Annotation(
        lambda qs, **kwargs: qs.annotate(
            amount_videos=Count("videos")
        ).distinct()
    )
    class Meta:
        model = User

以上将创建虚拟模型,这些模型在从VADE及其数量开始的过程中为我们提供了帮助,我们可以看到我们正在使用注释来添加金额_VIDEOS属性属性的VIDE的数量。

在我们的serializer.py文件中,我们可以将愉快的序列化器修改为以下内容:

import django_virtual_models as dvm
class UserSerializer(dvm.VirtualModelSerializer):
    videos = VideoSerializer(many=True)
    amount_videos = serializers.IntegerField(read_only=True)

    class Meta:
        model = User
        virtual_model = VirtualUser
        fields = ("name", "videos","amount_videos")

,我们的观点将如下:

class UserListAPIView(dvm.VirtualModelListAPIView):
    serializer_class = UserSerializer
    queryset = User.objects.all()

    def get_queryset(self):

        queryset = super().get_queryset()
        for query in connection.queries:
            print("Query:", query)
        reset_queries()
        return queryset

在上面,我们已经在使用一般QuerySet,因为我们的库已经谨慎地解决了我们的Vade问题以及视频的数量。因此,当我们访问路线并查看终端时,我们可以看到类似的东西:

Query: {'sql': 'SELECT DISTINCT "core_user"."id", "core_user"."name", COUNT("core_video"."id") AS "amount_videos" FROM "core_user" LEFT OUTER JOIN "core_video" ON ("core_user"."id" = "core_video"."user_id") GROUP BY "core_user"."id", "core_user"."name"', 'time': '0.001'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" IN (1, 2, 3)', 'time': '0.000'}

我们可以看到我们回到了2个查询的数量,因为在获取数据的查询中已经计数视频。冷却!?

已开发的代码:Repositório

反思

Django Virtual Model
Django Rest Framework
Django official