内容简介:如果一个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 区别对待
- 甲骨文裁员潮两面看 对待员工态度显现企业格局
- 欧洲区块链合作伙伴关系:欧洲正在认真对待分布式账本技术
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Domain-Driven Design Distilled
Vaughn Vernon / Addison-Wesley Professional / 2016-6-2 / USD 36.99
Domain-Driven Design (DDD) software modeling delivers powerful results in practice, not just in theory, which is why developers worldwide are rapidly moving to adopt it. Now, for the first time, there......一起来看看 《Domain-Driven Design Distilled》 这本书的介绍吧!