Django中的select_related和prefetch_related方法对比

模型定义

class Author(models.Model):
    """作者模型"""
    name = models.CharField(max_length=100)
    age = models.IntegerField()
    email = models.EmailField()

    class Meta:
        db_table = 'author'


class Book(models.Model):
    """图书模型"""
    name = models.CharField(max_length=300)
    pages = models.IntegerField()
    price = models.FloatField()
    rating = models.FloatField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)

    class Meta:
        db_table = 'book'


class BookOrder(models.Model):
    """图书订单模型"""
    book = models.ForeignKey("Book", on_delete=models.CASCADE)
    price = models.FloatField()
    create_time = models.DateTimeField(auto_now_add=True, null=True)

    class Meta:
        db_table = 'book_order'

select_related

在开发过程中,经常会用到一个表的外键所引用的表的内容,比如下面的第一种写法,对于每一本书,都会去单独查询他们的外键所引用的表数据,造成多次访问数据库,效率低下。
select_related就是用来解决这个问题的,它相当于执行了一次联合查询操作,查询后会同时把外键引用的表的数据存入内存中,因此总共只做了一次查询操作,就像下面的第二种写法所示。

# 1.多次查询
books = Book.objects.all()
for book in books:
    # 这样写会在每轮循环中查找当前的book所引用的author,有几次循环就会增加几次SQL查询
    print(book.author.name)

# 2.一次查询同时获取作者信息
books = Book.objects.select_related('author')
for book in books:
	# 因为select_related已经把数据存储到了内存中,所以下面的代码不会再进行SQL操作
    print(book.author.name)

prefetch_related

上面的select_related已经可以解决"正向"的外键查询,但是对于被引用的表,例如在Book表中我们希望能够查询引用它的BookOrder表的信息就做不到了。这时候就需要使用prefetch_related,例如使用下面的写法:

# bookorder_set的命名是因为表名为BookOrder,我们转化成小写形式,然后再加上_set表示引用方是多方,这是Django规定的命名方式
books = Book.objects.prefetch_related('bookorder_set')
for book in books:
    print('=' * 30)
    print(book.name)
    # 因为使用了prefetch_related,所以下面的代码在访问bookorder_set的时候其实就是在访问内存
    orders = book.bookorder_set.all()
    for order in orders:
        print(order.id)

prefetch_related其实也可以实现上面的正向查询功能,但是prefetch_relatedselect_related相比还是有很大不同的,因为prefetch_related其实是进行了两步操作:第一步先把所有的book从表中取出来,然后通过book的信息进行第二次的查找得到bookorder的信息。所以如果是正向查询的话更推荐使用select_related,可以少查询一次。

prefetch_related的有条件查询

需要注意的是,通过prefetch_related查找出来的bookorder_set不可以进行再过滤,否则之前的查询会失效,又会和没有进行prefetch_related一样对每一个循环单独执行SQL语句,因此,对于这种情况,需要使用下面的写法:

# 增加一个要求:选出售价大于等于90的图书
from django.db.models import Prefetch
# 在Prefetch的queryset中写出查询的条件,提前进行过滤
prefetch = Prefetch('bookorder_set', queryset=BookOrder.objects.filter(price__gte=90))
books = Book.objects.prefetch_related(prefetch)
for book in books:
    print('=' * 30)
    print(book.name)
    orders = book.bookorder_set.all()
    for order in orders:
        print(order.id)

你可能感兴趣的:(Django)