一次查询性能提高40倍的经历
2017-02-09 11:12
218 查看
背景说明
数据库:MongoDB数据集:
A:字段数不定,这里主要用到的两个UID和Date
B:三个字段,UID、Date、Actions。其中Actions字段是包含260元素JSON数组,每个JSON对象有6个字段。共有数据800万条左右。
业务场景:求平均数
通过组合条件从A数据表查询出(UID,Date)列表,最多可能包含数万条记录;
然后用第1步的结果从B中查询出对应的数据
用第2步结果去Actions的某个固定位置的元素的进行计算
进化过程
在这里使用Python演示
最直接想到的方法
根据上面的业务场景描述,最容易想到的解决方法就是from pymongo import MongoClient # 连接数据库 db = MongoClient('mongodb://127.0.0.1:27017')['my_db'] # 简化的查询数据集A的条件 filter = {...} # 查询Collection A a_cursor = db.a.find(_filter) a_docs = [x for x in a_cursor] # 变量的初始定义 count = 0 total = 0 # 加入需要用到的元素为第21个 index = 20 # 查询Collection B,同时做 4000 累加 for a_doc in a _docs: b_doc = db.b.find_one({'uid':a_doc['uid'], 'date': a_doc['date']}) # 只有能查到相应的结果时,才可以 if b_doc is not None: total += b_doc['actions'][20]['number'] count += 1 # 求平均数 if count > 0 : avg = total/count1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
实现难度当然是最低的,可是整个任务在第一步只有1万条左右的返回时,消耗的时间竟然达到了惊人38秒。当然这是已经加了索引的结果,否则可能都无法得到结果了。
减少查询次数
瓶颈显而易见,在循环中查询Collection B,增加了网络开销,自然也就增加时间,如果一次查询出所有结果,自然会大大提高效率。也就是说,我要把第一步的结果作为条件一次性传递,做一个$in操作。可是怎么才能做到呢?如果在uid和date上分别做$in操作,那么返回的结果就会是二者单独做$操作的合集,很显然这和要求是不符的。 经过上面的分析,似乎进入了死胡同。其实答案也基本显现了,需要有一个字段可以满足上面的要求,那么这个字段就是uid和date的合体,就命名为uid_date。uid_date是一个新字段,在B中并不存在,在使用之前需要将数据库现有的数据做一下处理。处理完毕改造程序:
# 下面的只体现和本次修改相关的内容 uid_date_list = [] for a_doc in a_docs: uid_date_list.append(a_doc['uid'] + '_' + a_doc['date']) # 查询B b_cursor = db.b.find({'uid_date':{'$in':uid_date_list}}) # 下面就是取出结果,求平均数 ...1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
这一番改造颇费时间,主要是前期的数据处理。代码改造完毕,执行下看看吧。
可是,可是…… 45秒
我做错了什么?!
增加返回记录数
我还是坚信上面的优化思路是对的,现在看看数据库能给一些什么线索吧。 登录到数据库服务器,找到MongoDB的日志/data/mongodb/logs/mongod.log。仔细查找,发现在查询数据集B时有很多getMore命令。这就奇怪了,我是一次性查询,为什么还有getMore。赶紧查下官方的文档,然后发现了下面的内容:
batcSize参数指定了每次返回的个数,默认的101个。那看来这个应该是问题所在。找下pymongo的文档,也可以设置这个参数,那就设个大的吧10000。再次改造程序如下:
# 增加batch_size b_cursor = db.b.find({'uid_date':{'$in': uid_date_list}}, batch_size=10000)1
2
1
2
这次总该可以了。
嗯,好了一些,降到了20秒左右。可是,这离1秒只能还差距20倍呢。
返回值减负
当日不能放弃,继续通过日志查找线索,发现还是有很多getMore。通过各方查找,发现mongodb每次最多返回16M的记录,通过getMore日志的比对,发现的确如此。由于B中每条记录的过去庞大,每次只能几百条记录,因此要一次多返回,那就必须要减少每次返回的记录数。因为在计算时,只用了特定索引位置上的数据,所以只返回该条记录就可以了。最后的代码就不再写了,具体可以参考官方文档的实例。
相关文章推荐
- 一次查询性能提高40倍的经历
- MongoDB数据库查询性能提高40倍的经历分享
- 提高Web应用程序的性能--我的一次优化经历
- 不断优化配置,逐步提高性能——我的一次性能测试经历
- 不断优化配置,逐步提高性能——我的一次性能测试经历
- 不断优化配置,逐步提高性能——我的一次性能测试经历
- 对多表联合查询的SQl语句的改进,大幅提高查询性能
- 对多表联合查询的SQl语句的改进,大幅提高查询性能 (转)
- 利用Oracle执行计划机制提高查询性能
- 使用索引和统计特性来提高数据库的查询性能(转)
- 利用Oracle执行计划机制提高查询性能
- 提高查询系统的性能
- 一次网站性能排查的经历
- 减少oracle sql回表次数 提高SQL查询性能
- 一次网站性能排查的经历
- oracle 执行计划的初试 利用Oracle执行计划机制提高查询性能
- ADO.NET 2.0 中的新增 DataSet 功能--性能提高40倍?(downmoon翻译自MSDN)
- Oracle如何实现利用实体化视图提高查询性能
- 利用Oracle执行计划机制提高查询性能
- SQL Server 2005中利用临时表和@@RowCount提高分页查询存储过程性能