c语言编程笔录

首页 >   > 笔记大全

笔记大全

Django框架中的ORM优化技巧

更新时间:2023-10-17

什么是ORM

ORM全称是Object Relational Mapping(对象关系映射),它是一种将对象和关系数据库进行映射的技术,通过它可以将对数据库的操作转换成对对象的操作,使得开发者可以像操作对象一样操作数据库。

ORM提供了更加面向对象的方式来操作数据库,使得代码更加优雅、简洁,同时也可以减少SQL注入攻击的风险。Django框架自带了ORM工具,可以让你更好地与数据库交互。

优化查询的N+1问题

Django ORM的一个常见问题是"N+1"查询问题。 当你有一个包含嵌套对象或关系的查询时,ORM将难以避免执行多个查询来检索与每个嵌套对象相关的数据。这是一个典型的SQL开销问题,当我们的数据量变大时,性能的影响会逐渐显现。

例如,我们有一个学生模型和一个课程模型,每个学生有许多课程,如下所示:


class Course(models.Model):
    name = models.CharField(max_length=100)

class Student(models.Model):
    name = models.CharField(max_length=100)
    courses = models.ManyToManyField(Course)

如果我们试图获取所有的学生以及每个学生所拥有的所有课程,我们通常会这样写:


students = Student.objects.all()
for student in students:
    courses = student.courses.all()
    # 处理课程...

上面的代码会引起N+1查询问题,当我们查询了N个学生时,会导致N+1个查询,会把数据库查询的次数提升到一个不可接受的程度,因此我们需要对这个问题进行优化。

优化方法是使用.select_related()和.prefetch_related()方法,这些方法允许我们在单个查询中同时获取关联模型的对象,并且通常会比通过多个查询获取内容更快。

使用.select_related()方法可以解决只跨越外键关系的问题,例如:


students = Student.objects.select_related('courses').all()
for student in students:
    courses = student.courses.all()
    # 处理课程...

上述代码将返回所有学生对象,以及每个学生对象的相关联的课程,这将导致一个查询来检索所有学生以及相关联的课程对象。 然后,每个学生对象都可以访问其关联的课程对象,而不需要再次查询数据库。

使用.prefetch_related()方法可以解决跨越多个关系的查询问题, 例如,我们选择从与当前实例关联的所有对象中获取对应的外键子集。 就像这样:


students = Student.objects.prefetch_related('courses').all()
for student in students:
    courses = student.courses.all()
    # 处理课程...

上述代码将返回所有学生对象,以及每个学生对象的所有相关联的课程对象。 这将导致两个查询:一个检索所有学生,一个检索所有相关联的课程。 注意,每个查询都被缓存到内存中,以便在操作时能够快速地访问它们。

使用QuerySet的defer()和only()方法来延迟查询

有时我们的模型包含许多字段,但是我们只需要在某些场合查询到某些字段,此时我们可以使用QuerySet的defer()和only()方法。这两个方法的作用是将查询分为多个阶段。在第一阶段中,它会选择我们要检索的列,而在第二阶段中,它才会检索无需同时检索的数据。

例如,如果我们有一个Person模型,其中包含了许多字段:


class Person(models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField()
    email = models.EmailField()
    phone = models.CharField(max_length=15)

对于上面的模型,我们可以用only()方法来选择要获取的字段,这有助于提高性能。


person = Person.objects.only('name', 'age').get(id=1)
print(person.email) # 这个语句不会引起查询

上述代码将只查询person对象的”name"和”age"字段并返回一个对象,而以后如果我们需要查询"email"或"phone"字段时,将会执行第二个查询。这样就可以快速地查询到所需的数据,而不是把所有的数据都一次性查询出来。

我们还可以使用defer()方法将数据延迟到我们需要它时再加载,这样可以减少加载时间。例如,我们不希望在我们获取第一个person对象时加载"email"和"phone"字段,而是在后面的流程中使用它们时再加载:


all_people = Person.objects.defer('email', 'phone')
for person in all_people:
    print(person.name)
    print(person.age)
    print(person.email)  # 这里才会加载 email 字段
    print(person.phone)  # 这里才会加载 phone 字段

上述代码将只查询person对象的"name"和"age"字段,而以后如果我们需要查询"email"或"phone"字段时,将会执行第二个查询。这样就可以快速地查询到所需的数据,而不是把所有的数据都一次性查询出来。

使用Django的缓存机制来提高查询效率

除了前面提到的方法,Django还自带了帮助我们提高查询效率的缓存机制。缓存机制主要是将查询结果进行缓存,避免频繁查询数据库,从而提高了查询效率,节省了时间。

首先需要在settings.py中配置缓存后端,例如:


CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

上述配置表示使用Memcached作为缓存后端,缓存的位置在本地127.0.0.1:11211端口。

接下来定义一个带备用参数的缓存装饰器,例如:


from django.core.cache import cache

def cache_it(timeout=None, key_prefix=''):
    def decorator(func):
        def wrapper(*args, **kwargs):
            key = f'{key_prefix}{func.__name__}_{args}_{kwargs}'
            result = cache.get(key)
            if result is None:
                result = func(*args, **kwargs)
                cache.set(key, result, timeout=timeout)
            return result
        return wrapper
    return decorator

上述代码定义了一个带备用参数的缓存装饰器,它将缓存结果并在一定时间内使用它。

我们可以将此装饰器应用到我们的视图函数或者查询函数中,例如:


@cache_it(timeout=600)
def get_students():
    return Student.objects.prefetch_related('courses').all()

def student_view(request):
    students = get_students()
    return render(request, 'student_view.html', {'students': students})

上述代码将使用cache_it()装饰器将get_students()函数进行缓存,以便我们可以在需要时快速地检索所有学生的数据。缓存将在600秒后过期并被删除。

这样,当我们再次执行get_students()函数时,将会返回缓存的结果,而不是再次查询数据库,这样提高了效率,并且减少了对数据库的负载。