django ORM查询优化

一.select_subclasses

官方文档:https://django-model-utils.readthedocs.io/en/latest/managers.html
select_subclasses是封装在一个InheritanceManager管理器中。

它允许对该基本模型的查询返回实际适当子类型的异构结果,而无需任何其他查询,可以理解为select_subclasses会把模型对象转换为子类来查询,获取每一个子类中的数据
举例:

# 地点
class Place(models.Model):
    objects = InheritanceManager()
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)


# 餐厅
class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)


# 广场
class Square(Place):
    flower = models.TextField()
    balloon = models.TextField()

上述Square和Restaurant都继承自Place,这里Restaurant和Square并没有设置外键关联到Place,只是单纯继承,如果我想查Restaurant和Square的数据,语句如下:

def query(request):
    places = Square.objects.all()
    restaurant = Restaurant.objects.all()
    print(list(places) + list(restaurant))
    return HttpResponse('ok')

在这里插入图片描述

使用select_subclasses:

def query(request):
    place = Place.objects.select_subclasses()
    print(place)
    return HttpResponse('ok')

结果是一样的:select_subclasses会把Place转成子类进行查询
在这里插入图片描述
这里会有一个坑:
如果Place的id在Square和Restaurant共同出现,就会造成一个问题,那就是数据查询出来的时候不对,
在这里插入图片描述
在这里插入图片描述
用我们上述语句去查的话得出来的结果全部是Restaurant对象,,原则上不会造成这样的情况出现,因为这里是我手动修改的数据,真是场景中应该不会有这种问题,这也是我最近调试遇到的一个坑:
在这里插入图片描述
get_subclass:
InheritanceManager还提供了一个替代get()方法的子类获取方法:

def query(request):
    place = Place.objects.get_subclass(id=6)
    print(place)
    return HttpResponse('ok')

二.values

简单创建两张表:

class Publish(models.Model):
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()
    def __str__(self):
        return self.name

# 书籍表
class Book(models.Model):
    title = models.CharField(max_length=32)
    good = models.IntegerField(default=0)  # 点赞
    comment = models.IntegerField(default=0)  # 评论
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)

使用all查询:

def query(request):
AuthorDetail.objects.create(birthday=datetime.now(),telephone='14513256456',addr='北京')
    books = Book.objects.all()
    for book in books:
        print(book.title,book.publish.name)
    return HttpResponse('ok')

打印结果,这里注意看打印的sql:
在这里插入图片描述
使用values查询:

def query(request):
    books = Book.objects.all().values('title','publish__name')
    print(books)
    return HttpResponse('ok')

在这里插入图片描述
上面通过控制台打印可以看到,输出的sql是不一样的,使用all查询明显是多查询了一次,通过values进行了连表操作,效率就高一些。

三.select_related

返回一个QuerySet将“遵循”外键关系的,在执行查询时选择其他相关对象数据。这可以提高性能,从而导致单个更复杂的查询,但意味着以后使用外键关系将不需要数据库查询。

简单创建两张表:

# 作者表
class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    sex = models.CharField(max_length=32, default='male')
    authorDetail = models.OneToOneField(to="AuthorDetail", on_delete=models.CASCADE)

    def __str__(self):
        return self.name

# 作者详细信息表
class AuthorDetail(models.Model):
    birthday = models.DateField()  # 出生日期
    telephone = models.CharField(max_length=32)  # 电话
    addr = models.CharField(max_length=64)  # 地址

    def __str__(self):
        return self.addr

以下示例说明了普通查找和select_related()查找之间的区别 。这是标准查询:
一对一:

def query(request):
    # 查询id 为1的人的手机号
    author = Author.objects.get(id=1)
    print(author.authorDetail.telephone)
    print('*' * 60)
    author = Author.objects.select_related('authorDetail').get(id=1)
    print(author.authorDetail.telephone)
    return HttpResponse('ok')

结果:
在这里插入图片描述
使用了select_related之后,里面的参数写的是关系字段的名称,那么就会先进行JOIN语句操作,通过减少SQL查询的次数来进行优化、提高性能,效率高一些,但是他用在外键或者一对一的关系上。 也接受无参数的调用,Django会尽可能深的递归查询所有的字段。但注意有Django递归的限制和性能的浪费。

外键:

两张表:

# 出版社表
class Publish(models.Model):
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()

    def __str__(self):
        return self.name


# 书籍表
class Book(models.Model):
    title = models.CharField(max_length=32)
    good = models.IntegerField(default=0)  # 点赞
    comment = models.IntegerField(default=0)  # 评论
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)

普通查询:

def query(request):
    books = Book.objects.all()
    for book in books:
        print(book.publish.name,book.title)
    return HttpResponse('ok')

结果:
在这里插入图片描述
select_related查询:

def query(request):
    books = Book.objects.all().select_related('publish')
    for book in books:
         print(book.publish.name,book.title)
    return HttpResponse('ok')

结果:
在这里插入图片描述
小结:
select_related主要针一对一和多对一关系进行优化,产生一次查询,使用SQL的JOIN语句进行优化。

四.prefetch_related

这个方法和select_related非常类似,就是在访问多个表中数据的时候,减少查询的次数,这个方法是为了解决多对一多对多的关系的查询问题。
https://docs.djangoproject.com/en/3.0/ref/models/querysets/
返回一个QuerySet,它将自动为每个指定的查询分批检索相关对象。

创建表:

# 书籍表
class Book(models.Model):
    title = models.CharField(max_length=32)
    good = models.IntegerField(default=0)  # 点赞
    comment = models.IntegerField(default=0)  # 评论
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
    authors = models.ManyToManyField(to='Author', through='BookToAuthor')

# 作者表
class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    sex = models.CharField(max_length=32, default='male')
    authorDetail = models.OneToOneField(to="AuthorDetail", on_delete=models.CASCADE)

使用prefetch_related 查询:
查单个书本的作者:

def query(request):
    books = Book.objects.get(id=1)
    print(books.authors.all())
    print('*' * 50)
    books = Book.objects.prefetch_related('authors').get(id=1)
    print(books.authors.all())
    return HttpResponse('ok')

在这里插入图片描述
查所有书本的作者:

def query(request):
    books = Book.objects.all()
    for book in books:
        for author in book.authors.all():
            print(author.name)
    print('*' * 50)
    books = Book.objects.prefetch_related('authors','publish').all()
    for book in books:
        for author in book.authors.all():
            print(author.name)
    return HttpResponse('ok')

在这里插入图片描述后面还可以跟过滤条件:

def query(request):
    books = Book.objects.prefetch_related('authors').filter(authors__age=12)
    for book in books:
        print(book.title)
    return HttpResponse('ok')

在这里插入图片描述
所有书的出版社:

def query(request):
    books = Book.objects.prefetch_related('publish')
    for book in books:
        print(book.publish.name)
    return HttpResponse('ok')

在这里插入图片描述
注意事项:
在预查询数据中使用filter?

def query(request):
    books = Book.objects.prefetch_related('authors')
    for book in books:
        for author in book.authors.filter(age__gte=10):
            print(author.name)
    return HttpResponse('ok')

看下结果,可以看到产生了多次查询,这肯定是有问题,如果使用了filter,会把之前的预加载给清除掉重新向数据库发送请求,可以使用Prefetch来过滤需要操作的条件:
在这里插入图片描述
Prefetch:

from django.db.models import Prefetch
def query(request):
    books = Book.objects.prefetch_related(
    Prefetch('authors',queryset=Author.objects.filter(age__gte=10)))
    for book in books:
        for author in book.authors.all():
            print(author.name)
    return HttpResponse('ok')

结果:
在这里插入图片描述

五.only和defer

only:只提取某几个字段

def query(request):
    author = Author.objects.all().only('name') # 只提取name字段
    print(author)
    return HttpResponse('ok')

sql:
在这里插入图片描述
注意:如果后面你再提取其他字段则会发起数据库请求。id无法操作,下同defer

defer:过滤字段

def query(request):
    author = Author.objects.all().defer('name','age')
    print(author)
    return HttpResponse('ok')

在这里插入图片描述
注意:如果你将字段过滤掉但后续又操作这个字段的话会再次发起数据库请求。

如有异议或表达不清楚的可以加群交流:934244622

猜你喜欢

转载自blog.csdn.net/qq_39253370/article/details/108443148