一次查询性能提高40倍的经历

原创 2017年02月08日 23:44:57

背景说明

  1. 数据库:MongoDB
  2. 数据集:
    • A:字段数不定,这里主要用到的两个UID和Date
    • B:三个字段,UID、Date、Actions。其中Actions字段是包含260元素JSON数组,每个JSON对象有6个字段。共有数据800万条左右。
  3. 业务场景:求平均数
    1. 通过组合条件从A数据表查询出(UID,Date)列表,最多可能包含数万条记录;
    2. 然后用第1步的结果从B中查询出对应的数据
    3. 用第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,同时做累加
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/count

实现难度当然是最低的,可是整个任务在第一步只有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}})

# 下面就是取出结果,求平均数
...

这一番改造颇费时间,主要是前期的数据处理。代码改造完毕,执行下看看吧。
可是,可是…… 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)

这次总该可以了。

嗯,好了一些,降到了20秒左右。可是,这离1秒只能还差距20倍呢。

返回值减负

当日不能放弃,继续通过日志查找线索,发现还是有很多getMore。通过各方查找,发现mongodb每次最多返回16M的记录,通过getMore日志的比对,发现的确如此。由于B中每条记录的过去庞大,每次只能几百条记录,因此要一次多返回,那就必须要减少每次返回的记录数。因为在计算时,只用了特定索引位置上的数据,所以只返回该条记录就可以了。

最后的代码就不再写了,具体可以参考官方文档的实例

版权声明:原创文章,欢迎转载,转载请注明出处和原文链接

oracle提高查询性能的方法

1、选择最有效率的表名顺序  (只在基于规则的优化器中有效):   ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table) ...
  • luxideyao
  • luxideyao
  • 2013年11月19日 08:09
  • 6114

EF细节处提高性能

一、显式打开连接 1、.SaveChanges() 这个不需要关注连接的问题,因为不管之前你无论是修改、删除、新增,只有一个SaveChanges(),一定是只用一个链接,并且系统还会自动...
  • bemavery
  • bemavery
  • 2015年01月28日 23:05
  • 5288

数据库索引为何能提高性能

本文只针对mysql进行相关方面的说明。 首先我要问,“数据索引是有序无序的?”。答:“当然是有序的了。” 再问,一个SQL语句可以有几个索引?你先别往下看结果,你这想下,可以使用几个索引...
  • qq_36976949
  • qq_36976949
  • 2016年12月16日 20:25
  • 346

使用索引不一定能提高查询性能

索引列的选择性不高。   我们假设典型情况,有表emp,共有一百万行数据,但其中的emp.deptno列,数据只有4种不同的值,如10、20、30、40。虽然 emp数据行有很多,ORACLE缺...
  • yanyu529584640
  • yanyu529584640
  • 2016年04月28日 10:49
  • 2272

优化hbase的查询提升读写速率优化案例及性能提升的几种方法

在初期,我们采用的逻辑是:将A表中的数据读取一行,根据其中的某个字段去组织一个GET,然后立刻提交,从B表取得要查询的字段的值,组织成一个PUT,并提交到A表。那么这么做的话, 1.完全发挥不出hb...
  • lisulong1
  • lisulong1
  • 2016年12月13日 23:14
  • 2012

记一次成功面试经历的问题和最近遇到的问题

多点触控的问题 面试问题
  • x2345com
  • x2345com
  • 2017年07月01日 00:11
  • 205

如何最大限度提高.NET的性能

优化 .NET的性能1)避免使用ArrayList。     因为任何对象添加到ArrayList都要封箱为System.Object类型,从ArrayList取出数据时,要拆箱回实际的类型。建议使用...
  • 21aspnet
  • 21aspnet
  • 2007年03月21日 00:51
  • 7581

一次气势恢宏的阅读经历

我跟着罗森博格在软件丛林里转了一圈。我看到了正在丛林中挣扎的远古巨兽,我看到了众多在软件花园中渐次凋零的花朵。近代科技革命运行于软件之上,但创造软件的艺术却仍旧藏身于暗处,秘不示人。即便对于那些专家也...
  • zyt157376
  • zyt157376
  • 2016年11月13日 10:57
  • 154

记一次当面试官的经历

昨天不小心当了一把面试官,感觉比自己被面试还紧张(瀑布汗),但是总体来说还算应对沉稳,嘻嘻。 面试者三十多岁,四年Android开发经验,按照他的年龄我的感觉是应该应聘项目经理之类更高的职位,把这个疑...
  • wenping1980
  • wenping1980
  • 2015年08月25日 23:28
  • 901

提高MySQL性能的7个技巧

原文:7 keys to better MySQL performance 作者:Peter Zaitsev 译者:Peter 译者注: 随着尺寸和负载的增长,MySQL的性能会趋于下降。...
  • dev_csdn
  • dev_csdn
  • 2017年11月02日 14:19
  • 19988
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:一次查询性能提高40倍的经历
举报原因:
原因补充:

(最多只允许输入30个字)