若是一个web请求须要花费几秒,99%是由于数据库没用好。 当使用ORM的时候,很天然地会想要用python的思惟方式来处理数据查询,可是这种思惟方式会杀死你的性能。改用子查询(subqueries)和annotations,以sql的思惟思考,能够大幅度提升你的web性能。python
有一天你打开Datadog,看到一张这样的图: web
对于一次web请求,数据库分配到的工做越多,数据库请求次数越少,效率越高。sql
若是将这644次数据库请求转换成一次,响应速度能够提升将近40倍。 数据库
有一个City model,其中有一个计算城市人口密度的方法density。django
class City(models.Model):
state = models.ForeignKey(State, related_name='cities')
name = models.TextField()
population = models.DecimalField()
land_area_km = models.DecimalField()
def density(self):
return self.population / self.land_area_km
复制代码
想要计算一个城市的人口密度,下面这种方式是很天然就能想到的:微信
>>> illinois = State.objects.get(name='Illinois')
>>> chicago = City.objects.create(
name="Chicago",
state=illinois,
population=2695598,
land_area_km=588.81
)
>>> chicago.density()
4578.04...
复制代码
问题出在当咱们想要查询出全部拥挤(密度大于4000)的城市时:网络
class City(models.Model):
...
@classmethod
def dense_cities(cls):
return [
city for city in City.objects.all()
if city.density() > 4000
]
复制代码
若是只有5%的城市是拥挤的,那么将会有95%的数据最终会被丢弃。**在数据中过滤,必定是比将数据导入内存,而后让Python过滤效率要高的!**对于不须要的数据,django都须要花时间完成额外、无心义的操做:将数据转换成model实例。对于数据量小的应用到没什么,可是一旦数据库一大,对性能照成的影响是巨大的。函数
objects = CitySet.as_manager()这一行表示对City这一model使用自定义的ModelManager,这里不展开讲了,有兴趣能够本身搜索一下。 关于annotate的使用,请参考今天一块儿发的另外一篇文章:Django annotation,减小IO次数利器。性能
class CitySet(models.QuerySet):
def add_density(self):
return self.annotate(
density=F('population') / F('land_area_km')
)
def dense_cities(self):
self.add_density().filter(density__gt=4000)
class City(models.Model):
...
objects = CitySet.as_manager()
复制代码
annotate(density=F('population') / F('land_area_km'))中的F aggregate函数表示获取population和land_area_km的值。spa
self.annotate(
density=F('population') / F('land_area_km')
)
复制代码
表示对于一个queryset,给他其中的每一项object,加上一个density字段,值为population /land_area_km。
>>> City.objects.dense_cities().values_list('name', 'density')
<QuerySet [("New York City", Decimal('10890.23')), ...]>
# Reverse descriptor
>>> illinois.city.dense_cities().values_list('name', 'density')
<QuerySet [("Chicago", Decimal('4578.04')), ...]>
复制代码
解释一下:
City.objects.dense_cities().values_list('name', 'density')
复制代码
这个查询语句的queryset是全部的city object,应该是直接用City这个model调用objects。先调用annotate(density=F('population') / F('land_area_km')),给每一个object加上density这个字段,最后筛选出density大于4000的。
illinois.city.dense_cities().values_list('name', 'density')
复制代码
这个查询语句的queryset是illinois州的全部城市。
这种方法比前面循环的方法效率高多了,由于IO只有一次。
一次查询效率比屡次查询高。 杀死django性能最简单的方式就是在for循环中使用query。
要筛选出全部存在dense城市的州:
[
state for state in State.objects.all()
if state.cities.dense_cities().exists()
]
复制代码
相似这种,exists()会进行一次额外的查询,这会累计不少次毫秒级的查询。加起来的时间也是很可观的。能够用subquery解决这个问题。
最基本的使用方法:
state_ids = City.objects.dense_cities().values('state_id')
State.objects.filter(id__in=Subquery(state_ids))
// 或者也能够把Subquery省略掉
State.objects.filter(id__in=state_ids)
复制代码
这样就把不少次的exists查询下降到了一次。
更进一步,和前面说过的annotate结合起来:
class StateSet(models.QuerySet):
def add_dense_cities(self):
return self.annotate(
has_dense_cities=Exists(
City
.objects
.filter(state=OuterRef('id'))
.dense_cities()
)
)
class State(models.Model):
...
objects = StateSet.as_manager()
复制代码
filter(state=OuterRef('id'))就是筛选出 state object的全部city,而后调用dense_cities筛选dense城市,而后调用Exists聚合函数,返回True或False。add_dense_cities就给state queryset里的每个object加上了一个has_dense_cities字段。
最后使用这个查询:
State.objects.add_dense_cities().filter(has_dense_cities=True)
复制代码
提升数据库查询效率的一个重要原则就是下降IO查询次数,尽可能避免使用for循环,试试annotate和subquery吧!
关注个人微信公众号