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
相关推荐
卓怡学长2 分钟前
w304基于HTML5的民谣网站的设计与实现
java·前端·数据库·spring boot·spring·html5
冰^20 分钟前
MySQL VS SQL Server:优缺点全解析
数据库·数据仓库·redis·sql·mysql·json·数据库开发
电商数据girl31 分钟前
产品经理对于电商接口的梳理||电商接口文档梳理与接入
大数据·数据库·python·自动化·产品经理
Spring小子1 小时前
黑马点评商户查询缓存--缓存更新策略
java·数据库·redis·后端
Tech Synapse3 小时前
人脸识别考勤系统实现教程:基于Face-Recognition、OpenCV与SQLite
人工智能·opencv·sqlite
溜溜刘@♞3 小时前
数据库之mysql优化
数据库·mysql
uwvwko3 小时前
ctfhow——web入门214~218(时间盲注开始)
前端·数据库·mysql·ctf
柯3494 小时前
Redis的过期删除策略和内存淘汰策略
数据库·redis·lfu·lru
Tiger_shl4 小时前
【Python语言基础】24、并发编程
java·数据库·python
<<4 小时前
基于Django的权限管理平台
后端·python·django