Django ORM select_related 和 prefetch_related

在数据库有外键的时候,使用 select_related() 和 prefetch_related() 可以很好的减少数据库请求的次数,从而提高性能。

以例子来说明,两者的用法:

from django.db import models
 
class Province(models.Model):
    name = models.CharField(max_length=10)
    def __unicode__(self):
        return self.name
 
class City(models.Model):
    name = models.CharField(max_length=5)
    province = models.ForeignKey(Province)
    def __unicode__(self):
        return self.name
 
class Person(models.Model):
    firstname  = models.CharField(max_length=10)
    lastname   = models.CharField(max_length=10)
    visitation = models.ManyToManyField(City, related_name = "visitor")
    hometown   = models.ForeignKey(City, related_name = "birth")
    living     = models.ForeignKey(City, related_name = "citizen")
    def __unicode__(self):
        return self.firstname + self.lastname

select_related:
对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related 来对QuerySet进行优化。 在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。

如果我们需要打印数据库中的所有市及其所属省份,最直接的做法是:

>>> citys = City.objects.all()
>>> for c in citys:
...   print c.province

这样会导致线性的SQL查询,如果对象数量n太多,每个对象中有k个外键字段的话,就会导致n*k+1次SQL查询。在本例中,因为有3个city对象就导致了4次SQL查询:

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
 
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1 ;
 
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 2 ;
 
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM `QSOptimize_province`

如果我们使用select_related()函数:

>>> citys = City.objects.select_related().all()
>>> for c in citys:
...   print c.province

就只有一次SQL查询,显然大大减少了SQL查询的次数:

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, 
`QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM`QSOptimize_city` 
INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`) ;

prefetch_related()
对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。或许你会说,没有一个叫OneToManyField的东西啊。实际上 ,ForeignKey就是一个多对一的字段,而被ForeignKey关联的字段就是一对多字段了。
prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样。(前者是通过 IN 语句)后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增加和内存占用的增加。若有n个对象,每个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。

prefetch_related()的解决方法是,分别查询每个表,然后用Python处理他们之间的关系。我们要获得湖北的所有城市名,可以这样

>>> hb = Province.objects.prefetch_related('city_set').get(name__iexact=u"湖北省")
>>> for city in hb.city_set.all():
...   city.name

触发的 SQL 查询:

SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM `QSOptimize_province` 
WHERE `QSOptimize_province`.`name` LIKE '湖北省' ;

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` 
FROM `QSOptimize_city` 
WHERE `QSOptimize_city`.`province_id` IN (1);

我们可以看见,prefetch使用的是 IN 语句实现的。

总结:
因为select_related()总是在单次SQL查询中解决问题,而prefetch_related()会对每个相关表进行SQL查询,因此select_related()的效率通常比后者高。

发布了44 篇原创文章 · 获赞 0 · 访问量 3921

猜你喜欢

转载自blog.csdn.net/cpxsxn/article/details/104759607