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
相关推荐
ZTLJQ1 小时前
数据的基石:Python中关系型数据库完全解析
开发语言·数据库·python
升鲜宝供应链及收银系统源代码服务2 小时前
《IntelliJ + Claude Code + Gemini + ChatGPT 实战配置手册升鲜宝》
java·前端·数据库·chatgpt·供应链系统·生鲜配送
跟着珅聪学java2 小时前
js编写中文转unicode 教程
前端·javascript·数据库
小江的记录本2 小时前
【Redis】Redis全方位知识体系(附《Redis常用命令速查表(完整版)》)
java·数据库·redis·后端·python·spring·缓存
还是做不到嘛\.3 小时前
Dvwa靶场-SQL Injection
数据库·sql·web安全
楼田莉子4 小时前
MySQL数据库:MySQL的数据类型
数据库·学习·mysql
2401_879693874 小时前
数据分析与科学计算
jvm·数据库·python
LJianK14 小时前
java封装
java·前端·数据库
知识分享小能手5 小时前
MongoDB入门学习教程,从入门到精通,MongoDB查询(4)
数据库·学习·mongodb
LSL666_6 小时前
MybatisPlus条件构造器(上)
java·数据库·mysql·mybatisplus