- 查询集(QuerySet)缓存
- 外键关系对象和逆关系对象的提前查询和缓存
- 避免在循环中进行数据库查询
- 用
iterator()
遍历减少缓存 - 尽量使用数据库操作提高效率
- 使用
F()
直接操作模型字段的值 - 使用数据库聚合
- 使用
values()
或values_list()
获取只包含特定字段的列表、字典或元组 - 使用
defer()
或only()
获取只包含特定字段的查询集 - 当不需要查询集的内容时使用
count()
或exists()
- 使用
delete()
、update()
或bulk_create()
进行批量操作 - 直接使用外键
ORM全称是Object Relational Mapping(对象关系映射),它在编程中把面向对象的概念跟数据库中表的概念对应起来,定义一个对象,就对应着一张表,这个对象的实例,就对应着表中的一条记录。ORM封装了底层的数据库操作,降低了编程难度,提高了代码的可读性和易维护性。
虽然ORM可以带来极大的便利,但使用不当会带来运行效率的问题。下面以Django ORM为例,给出一些性能优化要点。
查询集(QuerySet)缓存
Django的查询集是惰性求值的,重复调用时可能会重复计算,利用缓存可以避免不必要的重复计算。
print([p.name for p in Person.objects.all()]) # 查询集计算并缓存
print([p.name for p in Person.objects.all()]) # 查询集再次计算并缓存
queryset = Person.objects.all()
print([p.name for p in queryset]) # 查询集计算并缓存
print([p.name for p in queryset]) # 使用缓存的值
queryset = Person.objects.all()
print(queryset[0]) # 查询数据库
print(queryset[0]) # 再次查询数据库
queryset = Person.objects.all()
list(queryset) # 查询集计算并缓存
print(queryset[0]) # 使用缓存的值
print(queryset[0]) # 使用缓存的值
外键关系对象和逆关系对象的提前查询和缓存
Django计算查询集时不包括外键关系和逆关系,所以不会包含在缓存中。
person = Person.objects.get(id=1)
person.father # 获得外键对象并缓存
person.father # 使用缓存的值
person = Person.objects.get(id=1)
person.children.all() # 数据库查询
person.children.all() # 数据库再次查询
使用select_related()
和prefetch_related()
指定需要的对象,Django会提前查询并缓存。
# 忌
queryset = Person.objects.all()
for person in queryset:
person.father # 每个迭代的外键对象都要在数据库查询
# 宜
queryset = Person.objects.all().select_related('father') # 指定外键对象并缓存
for person in queryset:
person.father # 使用缓存的值
避免在循环中进行数据库查询
这是非常低效的做法,应该在循环之前先进行数据库查询。
# 忌
filtered = Person.objects.filter(first_name='Shallan', last_name='Davar')
for age in range(18):
person = filtered.get(age=age) # 每个迭代都要在数据库查询
# 宜
filtered = Person.objects.filter(
first_name='Shallan',
last_name='Davar',
age_gte=0,
age_lte=18,
)
lookup = {person.age: person for person in filtered}
for age in range(18):
person = lookup[age] # 没有数据库查询
用iterator()
遍历减少缓存
查询集非常大而且只要遍历一次可以用这种方法。
# 不会缓存
for person in Person.objects.iterator():
# 一些操作
尽量使用数据库操作提高效率
# 忌
for person in Person.objects.all():
if person.age >= 18:
# 一些操作
# 宜
for person in Person.objects.filter(age__gte=18):
# 一些操作
使用F()
直接操作模型字段的值
这种方法不需要缓存。
# 忌
for person in Person.objects.all():
person.age += 1
person.save()
# 宜
from django.db.models import F
Person.objects.update(age=F('age') + 1)
使用数据库聚合
# 忌
max_age = 0
for person in Person.objects.all():
if person.age > max_age:
max_age = person.age
# 宜
max_age = Person.objects.all().aggregate(Max('age'))['age__max']
使用values()
或values_list()
获取只包含特定字段的列表、字典或元组
# 忌
age_lookup = {
person.name: person.age
for person in Person.objects.all()
}
# 宜
age_lookup = {
person['name']: person['age']
for person in Person.objects.values('name', 'age')
}
# 忌
person_ids = [person.id for person in Person.objects.all()]
# 宜
person_ids = Person.objects.values_list('id', flat=True)
使用defer()
或only()
获取只包含特定字段的查询集
queryset = Person.objects.defer('age') # 假设age计算开销大
for person in queryset:
print(person.id)
print(person.name)
queryset = Person.objects.only('name')
for person in queryset:
print(person.name)
当不需要查询集的内容时使用count()
或exists()
# 忌
count = len(Person.objects.all())
# 宜
count = Person.objects.count()
# 忌
exists = Person.objects.count() > 0
# 宜
exists = Person.objects.exists()
使用delete()
、update()
或bulk_create()
进行批量操作
# 忌
for person in Person.objects.all():
person.delete()
# 宜
Person.objects.all().delete()
# 忌
for person in Person.objects.all():
person.age = 0
person.save()
# 宜
Person.objects.update(age=0)
names = ['Jeff', 'Beth', 'Tim']
creates = []
for name in names:
creates.append(
Person(name=name, age=0)
)
Person.objects.bulk_create(creates)
直接使用外键
Django ORM 自动获取和缓存外键,不需要额外不必要的查询。
# 忌
father_id = Person.objects.get(id=1).father.id
# 宜
father_id = Person.objects.get(id=1).father_id