说明

考虑这样一个场景,有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的查询应该也是这样。