问题描述
成绩管理页面加载耗时较久,长达 7s 左右,严重影响用户体验
问题分析
首先对函数进行性能分析,通过 time.time() 函数获取当前时间,两次时间的差即为此语句消耗的时间。预计 map_answers() 函数比较耗时,因为该函数会取出所有的结果
第一次测试结果如下
发现第三条语句执行较慢,耗时 7s,查看 mongoengine 官方文档得知,获取结果数目时应该使用 count() 函数,使用 len() 函数会取出所有的结果,放置在本地缓存,所以耗时较长。这也就导致后面的 map_answers() 函数可以直接使用缓存,反而耗时较短。
修改后进行第二次测试
耗时最长的变成了第四条语句,表明记录迟早都会被全部取出来,此处修改意义不大。 注:all_answers 为 QuerySet,并不是具体数据,for 循环时才会取数据
猜测:mongoengine 将数据转化为 document 实例较慢
mongoengine 可以理解为对pymongo的封装,跟pymongo比,它最大的消耗在从pymongo查询的结果,转换为Document实例,这确实很费资源
mongoengine 在文档实例save的时候,会调用validate来验证每一个field, 这步没有必要,比较耗时
https://xwl-note.readthedocs.io/en/latest/software/mongoengine.html
mongoengine 有一个函数 as_pymongo(),使用此函数则不会把数据转化为对象,而是直接以 Python 字典的形式返回。使用 as_pymongo(),然后遍历 answers 进行测试,发现时间变化不大
猜测:SQL查询较慢
使用 explain() 函数进行分析,显示执行时间为 0ms
使用 Navicat 直接进行查询,耗时 7s 左右
产生此差异的原因是,在 mongoDB 中,explain() 函数所显示的执行时间应该是找到符合条件的数据的时间,而 Navicat 查询所显示的时间不仅包括查询的时间,还包括数据 bson 化所需的时间,这个时间性能分析工具没有统计出来。
综上分析,造成函数执行时间较长的根本原因是取出所有记录并转换为 bson 形式的数据耗时较长,怀疑是一条记录中包含的字段过多且比较复杂(包含 Document 嵌套)导致。
解决方案
- 该 API 的功能是求出每道题的平均分,那么可以直接使用 aggregate 操作求出平均值
- 使用 fields() 函数,返回指定字段
参考资料
- 【重点】MongoDB慢查询性能分析 https://www.cnblogs.com/Lifehacker/p/mongodb_slow_quey.html
- 4种方法解决MongoDB游标超时的问题 https://cloud.tencent.com/developer/article/1490538
- mongoengine中queryset触发网络访问机制剖析 https://www.cnblogs.com/AcAc-t/p/mongoengine_queryset_parse.html
- Mongoengine is very slow on large documents compared to native pymongo usage https://stackoverflow.com/questions/35257305/mongoengine-is-very-slow-on-large-documents-compared-to-native-pymongo-usage