Django一分钟:使用prefetch_related避免陷入大量的查询中导致严重的性能问题

本文将通过简单的示例介绍为什么要使用prefetch_related

准备工作

创建模型

python 复制代码
from django.db import models

class Product(models.Model):
    product_name = models.CharField(max_length=255)

class Component(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='components')
    code = models.CharField(max_length=255)

    class Meta:
        ordering = ['product']

创建数据

python 复制代码
>>> from api.models import Product
>>> p1 = Product.objects.create(product_name="产品1")
>>> p2 = Product.objects.create(product_name="产品2")
>>> from api.models import Component
>>> pc1 = Component.objects.create(product=p1, code="LS")
>>> pc2 = Component.objects.create(product=p1, code="LM")
>>> pc3 = Component.objects.create(product=p2, code="XYB")
>>> pc4 = Component.objects.create(product=p2, code="LXC")

settings.py文件中添加配置输出运行时执行的sql

python 复制代码
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'level': 'DEBUG',
        },
    },
}
python 复制代码
from api.models import Product
ps = Product.objects.all()
# SELECT "api_product"."id", "api_product"."product_name" FROM "api_product"; args=(); alias=default
    
for p in ps:
    print(p.components.all())
    
# SELECT "api_component"."id", "api_component"."product_id", "api_component"."code" FROM "api_component" WHERE "api_component"."product_id" = 1 ORDER BY "api_component"."product_id" ASC LIMIT 21; args=(1,);

# SELECT "api_component"."id", "api_component"."product_id", "api_component"."code" FROM "api_component" WHERE "api_component"."product_id" = 2 ORDER BY "api_component"."product_id" ASC LIMIT 21; args=(2,);

不使用prefetch_related我们获取所有"产品"执行了一次sql查询,然而当我们遍历想要获取所有产品的组件时,对每个产品都执行了一次sql查询。

我们的示例中只产品的数量只有两个,当产品的数量非常多,成百上千时,对每个产品你都要到数据库执行一次查询,这将导致严重的性能问题,也就是所谓的n+1问题。

python 复制代码
from api.models import Product
ps = Product.objects.prefetch_related("components")
# SELECT "api_product"."id", "api_product"."product_name" FROM "api_product"; args=(); alias=default

# SELECT "api_component"."id", "api_component"."product_id", "api_component"."code" FROM "api_component" WHERE "api_component"."product_id" IN (1, 2) ORDER BY "api_component"."product_id" ASC; args=(1, 2); alias=default
for p in ps:
    print(p.components.all())

可以清晰的看到总共执行了两次sql查询,在获取全部"产品"的时候通过一次额外的查询将所有产品的组件信息缓存起来,当我们需要的时候是从缓存中获取,不会执行任何的数据库查询

select_related只能用于一对一关系和外键关系,它是利用Join只进行一次SQL查询,但是它的局限也很明显,不能用于多对多,多对一等

python 复制代码
from api.models import Component
c = Component.objects.select_related("product").get(pk=1)

# SELECT "api_component"."id", "api_component"."product_id", "api_component"."code", "api_product"."id", "api_product"."product_name" FROM "api_component" INNER JOIN "api_product" ON ("api_component"."product_id" = "api_product"."id") WHERE "api_component"."id" = 1 LIMIT 21; args=(1,); alias=default
相关推荐
MonkeyKing_sunyuhua35 分钟前
ubuntu22.04 docker-compose安装postgresql数据库
数据库·docker·postgresql
天郁青35 分钟前
数据库交互的本地项目:后台管理系统
数据库·交互
马剑威(威哥爱编程)41 分钟前
MongoDB面试专题33道解析
数据库·mongodb·面试
小光学长1 小时前
基于vue框架的的流浪宠物救助系统25128(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
数据库·vue.js·宠物
零炻大礼包2 小时前
【SQL server】数据库远程连接配置
数据库
zmgst2 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
随心............2 小时前
python操作MySQL以及SQL综合案例
数据库·mysql
€☞扫地僧☜€2 小时前
docker 拉取MySQL8.0镜像以及安装
运维·数据库·docker·容器
CopyDragon2 小时前
设置域名跨越访问
数据库·sqlite
xjjeffery2 小时前
MySQL 基础
数据库·mysql