说明
考虑这样一个场景,有5亿条关系数据(from_id…to_id)可能存在重复,如何去重?
如果内存够大,那么去重非常简单,df.drop_duplicates(['from_id','to_id'])
就可以了;如果数据太大了呢?就只能放到数据库里执行了。
本篇补一个实际中碰到的坑。
内容
原始的数据类似:
id | from_id | to_id | content |
111 | 1 | 2 | 你好 |
222 | 1 | 2 | 中国 |
需求
id | from_id | to_id | content |
222 | 1 | 2 | 你好,中国 |
实际中碰到的问题是匹配效率非常低,大致是因为我用了循环去寻找 from_id, to_id记录。
例如,我读到了1万条新数据,里面有一万对from_id和to_id, 我要看看现有的结果表中是不是已经有这样的记录了,于是用了这样的笨办法:
...
for kv in kv_list:
try:
tem_res = collection.find_one(kv, keep_dict)
except:
tem_res = False
finally:
res_list.append(tem_res
本质上这是一个query_one的批量操作
,但是mongo好像只有批量的更新操作。这种批量的查询操作一般使用$in some_list
的方式操作,因此我把from_id和to_id重新生成一个pair_id。这会造成额外的空间占用,但想想还是有必要的吧。
我测试用的集合数据为1千万条。
1 生成pair_id
kv = {'from_id':{'$exists':True},'to_id':{'$exists':True},
'id':{'$exists':True}, 'pair_id': {'$exists':False}}
先按这样的条件选出没有创建pair_id的数据,我用远端操作(网络传输很费时),大约每1秒更新一万条(其实也还好)。
2 为新的pair_id建立索引
collection.index_information()
collection.create_index([('pair_id',1)])
collection.create_index([('from_id',1),('to_id',1)])
3 比较两种方式的处理时间
从库中随机挑选10万条,假设这10万条是新数据。
3.1 方法1:使用(from_id
,to_id
)进行循环查找
kv_list = json.loads(df2[['from_id','to_id']].to_json(orient='records'))
start = time.time()
method1 = lmongo2.query_one(dbname='results',cname='collection',
kv_list=kv_list)
print('takes %.2f' %(time.time()-start))
---
100条 7.51 未加索引 3.53 加索引 (这里有网络数据传输的部分,不太好评估,但加索引肯定是快的)
3.2 方法2:使用$in pair_id_list
进行查找
# 方法2 要查询的id放在list, 一次查询多个匹配
pair_id_list = list(df2['pair_id'])
m2_kv = [{'$in':pair_id_list}]
start = time.time()
method2 = lmongo2.query_many(dbname='results',cname='collection',
kv_list=m2_kv)[0]
print('takes %.2f' %(time.time()-start))
---
100条 0.12秒
两者结果是相同的
In [44]: set(method1[0].keys()) ==set(method2[0].keys())
Out[44]: True
结论
对于成千上万的数据库操作,不能使用for循环。只有在batch和batch之间使用for循环(收到网络、内存等的限制,这个是没办法的)。
另外query_many的操作是指匹配同时匹配n个id,而不是某个固定的条件。这时候只能用in。对于mysql的查询应该也是这样。