一次mongoengine查询速度慢的优化

情景以及定位慢处:

数据从30万到240万,原来查询post,现在查询GalleryPostposts,原来:
慢原来显示:
posts = Post.objects(tags__all=tags).order_by('-id').limit(per_page)
_user_ids = list(map(lambda _post: _post.user_id, posts))
_user_ids = list(set(_user_ids))
print("user_ids_time:", time.time() - user_list_time)  
#耗时8秒 
 

解析查询语句:

posts = Post.objects(tags__all=tags).order_by('-id').limit(per_page)
print(posts._query)
{'tags': {'$all': [ObjectId('5c531f47dd40ba6384d882f7'), ObjectId('5c531f47dd40ba6384d883c6')]}, '_cls': {'$in': ('Post', 'Post.GalleryPost', 'Post.VideoPost', 'Post.ArticlePost')}}
 

修改后语句:

posts = GalleryPost.objects(tags__all=tags).order_by('-id').limit(per_page)
print(posts._query)
{'tags': {'$all': [ObjectId('5c531f47dd40ba6384d882f7'), ObjectId('5c531f47dd40ba6384d883c6')]}, '_cls': 'Post.GalleryPost’} 
 

原因:

records = User.objects(first_name=‘Jack‘).order_by(‘ctime‘)
而后通过获取到的records就可以对其进行各种操作了:
 
if not records: # 判断数据是否为空
    if len(records) > 1: # 判断记录条数是否符合要求
        pk = records[0].pk # 获取首条记录的pk
        pk = records[0][‘ctime‘] # 获取收条记录的创建时间
使用逻辑是没有问题的,从业务逻辑上来说完全可以达到编码者的目的,然而却存在一个耗时较高、对mongodb访问过于平凡的隐藏坑。之前一直以为上面代码,程序在开始定义records时,即已经通过User.objects指定查找和排序条件执行完将记录结果赋值给了records,因而后面的records使用都是直接访问已经存在本地的返回结果进行的操作,然而事实上,上面这段代码,每一行都会触发一次对mongodb实时网络访问,总共是四次网络访问,至于records定义赋值的哪一行,反而没有对mongodb进行访问,远超多次的网络访问,不但增加了服务本身处理请求的总耗时,使其对网络和mongodb的波动更加敏感,而且使mongodb的访问量毫无必要的增加了好几倍,危害甚大。
 
records = User.objects(first_name=‘Jack‘).order_by(‘ctime‘) #records实际类型为:<class ‘mongoengine.queryset.queryset.QuerySet‘>
QuerySet对象的创建,并不需要去远程调用mongodb,而只是把查询的相关条件(first_name=‘Jack‘、order_by排序条件等)记录在QuerySet对象之中,在后面真正需要访问具体的记录属性时,才会根据条件去远程查询mongodb。
实际QuerySet对象创建时并不会立即去查询mongodb获取结果,而是在真正使用时,根据重载的使用行为再去查询mongodb,其查询mongodb的相关代码位于BaseQuerySet基类的_cursor函数。
 
所以对于User.objects返回结果其实更好的命名风格应该是:
query_set = User.objects(first_name=‘Jack‘).order_by(‘ctime‘)或者
qs = User.objects(first_name=‘Jack‘).order_by(‘ctime‘)
 
而为了避免之后对queryset的直接操作导致预期之外的多次网络请求mongodb,在获取query_set后,可以直接执行一次查询将其所有查询到的结果单独存到一个本地list对象中:
records = [item for item in query_set]
之后对records操作就不会再触发对mongodb的网络访问了,这么做相比之前唯一的缺陷就是如果本地使用records时耗时过长,可能导致本地数据和mongodb端不一致,不过对于绝大部分场景,查询完mongodb后,对其结果的引用应该会在很短的时间内完成,基本不会存在不一致的问题,应用者根据场景评估下即可。
 
 
 


 
 
 
 
 
 
 
 
 
posted @ 2019-03-19 11:14  Adamanter  阅读(889)  评论(0编辑  收藏  举报