内容简介:如果一个web请求需要花费几秒,99%是因为数据库没用好。当使用ORM的时候,很自然地会想要用python的思维方式来处理数据查询,但是这种思维方式会杀死你的性能。改用子查询(subqueries)和annotations,以sql的思维思考,可以大幅度提高你的web性能。有一天你打开Datadog,看到一张这样的图:红色的区域表示进行了数据库请求。这一次web请求进行了644次数据库请求!只有18.6%的时间在做真正有用的事。单次的数据库请求是很快的,但是这么多请求加起来就会严重拖慢web请求速度。 在d
如果一个web请求需要花费几秒,99%是因为数据库没用好。当使用ORM的时候,很自然地会想要用 python 的思维方式来处理数据查询,但是这种思维方式会杀死你的性能。改用子查询(subqueries)和annotations,以 sql 的思维思考,可以大幅度提高你的web性能。
有一天你打开Datadog,看到一张这样的图:
红色的区域表示进行了数据库请求。这一次web请求进行了644次数据库请求!只有18.6%的时间在做真正有用的事。单次的数据库请求是很快的,但是这么多请求加起来就会严重拖慢web请求速度。 在django这个上下文下,每一次数据库请求,都需要分配内存,model和数据库映射时,还需要序列化和反序列化,然后还要通过网络传输数据。
对于一次web请求,数据库分配到的工作越多,数据库请求次数越少,效率越高。
如果将这644次数据库请求转换成一次,响应速度可以提高将近40倍。
数据库查询性能清单
- 无论数据大小,请求次数是不是都是常数?
- 你是否只从数据库取真正需要的数据?
- 这个问题只能使用Python循环解决吗?
打破Python思维模式
有一个City model,其中有一个计算城市人口密度的方法density。
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实例。对于数据量小的应用到没什么,但是一旦数据库一大,对性能照成的影响是巨大的。
使用annotate
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的值。
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只有一次。
使用subquery
一次查询效率比多次查询高。 杀死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吧!
关注我的微信公众号
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- ORM-像对象一样对待数据
- 如何正确对待网络上大量的 “学习资料”
- 都是技术人员,还要分三六九等区别对待?
- 打压竞争对手?Firefox 搜索结果受 Google 区别对待
- 甲骨文裁员潮两面看 对待员工态度显现企业格局
- 欧洲区块链合作伙伴关系:欧洲正在认真对待分布式账本技术
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。